Use libvips' new lanczos3 kernel as default for image reduce
Deprecate interpolateWith method, now provided as an option
69
docs/api.md
@ -118,7 +118,7 @@ Do not process input images where the number of pixels (width * height) exceeds
|
|||||||
|
|
||||||
### Resizing
|
### Resizing
|
||||||
|
|
||||||
#### resize([width], [height])
|
#### resize([width], [height], [options])
|
||||||
|
|
||||||
Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified.
|
Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified.
|
||||||
|
|
||||||
@ -126,6 +126,42 @@ Scale output to `width` x `height`. By default, the resized image is cropped to
|
|||||||
|
|
||||||
`height` is the integral Number of pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width.
|
`height` is the integral Number of pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width.
|
||||||
|
|
||||||
|
`options` is an optional Object. If present, it can contain one or more of:
|
||||||
|
|
||||||
|
* `options.kernel`, the kernel to use for image reduction, defaulting to `lanczos3`.
|
||||||
|
* `options.interpolator`, the interpolator to use for image enlargement, defaulting to `bicubic`.
|
||||||
|
|
||||||
|
Possible kernels are:
|
||||||
|
|
||||||
|
* `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
|
||||||
|
* `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
|
||||||
|
* `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
|
||||||
|
|
||||||
|
Possible interpolators are:
|
||||||
|
|
||||||
|
* `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
|
||||||
|
* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), faster than bicubic but with less smooth results.
|
||||||
|
* `vertexSplitQuadraticBasisSpline`: Use the smoother [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48) to prevent "staircasing" when enlarging.
|
||||||
|
* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default).
|
||||||
|
* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2.
|
||||||
|
* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance but typically reduces performance by a factor of 3.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
sharp(inputBuffer)
|
||||||
|
.resize(200, 300, {
|
||||||
|
kernel: sharp.kernel.lanczos2,
|
||||||
|
interpolator: sharp.interpolator.nohalo
|
||||||
|
})
|
||||||
|
.background('white')
|
||||||
|
.embed()
|
||||||
|
.toFile('output.tiff')
|
||||||
|
.then(function() {
|
||||||
|
// output.tiff is a 200 pixels wide and 300 pixels high image
|
||||||
|
// containing a lanczos2/nohalo scaled version, embedded on a white canvas,
|
||||||
|
// of the image data in inputBuffer
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
#### crop([option])
|
#### crop([option])
|
||||||
|
|
||||||
Crop the resized image to the exact size specified, the default behaviour.
|
Crop the resized image to the exact size specified, the default behaviour.
|
||||||
@ -232,37 +268,6 @@ if its width or height exceeds the geometry specification*".
|
|||||||
|
|
||||||
Ignoring the aspect ratio of the input, stretch the image to the exact `width` and/or `height` provided via `resize`.
|
Ignoring the aspect ratio of the input, stretch the image to the exact `width` and/or `height` provided via `resize`.
|
||||||
|
|
||||||
#### interpolateWith(interpolator)
|
|
||||||
|
|
||||||
Use the given interpolator for image resizing, where `interpolator` is an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.bicubic`.
|
|
||||||
|
|
||||||
The default interpolator is `bicubic`, providing a general-purpose interpolator that is both fast and of good quality.
|
|
||||||
|
|
||||||
Possible interpolators, in order of performance, are:
|
|
||||||
|
|
||||||
* `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation), suitable for image enlargement only.
|
|
||||||
* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), faster than bicubic but with less smooth results.
|
|
||||||
* `vertexSplitQuadraticBasisSpline`: Use the smoother [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48) to prevent "staircasing" when enlarging.
|
|
||||||
* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default).
|
|
||||||
* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2.
|
|
||||||
* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance but typically reduces performance by a factor of 3.
|
|
||||||
|
|
||||||
[Compare the output of these interpolators](https://github.com/lovell/sharp/tree/master/test/interpolators)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
sharp(inputBuffer)
|
|
||||||
.resize(200, 300)
|
|
||||||
.interpolateWith(sharp.interpolator.nohalo)
|
|
||||||
.background('white')
|
|
||||||
.embed()
|
|
||||||
.toFile('output.tiff')
|
|
||||||
.then(function() {
|
|
||||||
// output.tiff is a 200 pixels wide and 300 pixels high image
|
|
||||||
// containing a nohalo scaled version, embedded on a white canvas,
|
|
||||||
// of the image data in inputBuffer
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Operations
|
### Operations
|
||||||
|
|
||||||
#### extract({ left: left, top: top, width: width, height: height })
|
#### extract({ left: left, top: top, width: width, height: height })
|
||||||
|
@ -6,6 +6,11 @@ Requires libvips v8.3.1
|
|||||||
|
|
||||||
#### v0.15.0 - TBD
|
#### v0.15.0 - TBD
|
||||||
|
|
||||||
|
* Use libvips' new Lanczos 3 kernel as default for image reduction.
|
||||||
|
Deprecate interpolateWith method, now provided as a resize option.
|
||||||
|
[#310](https://github.com/lovell/sharp/issues/310)
|
||||||
|
[@jcupitt](https://github.com/jcupitt)
|
||||||
|
|
||||||
* Take advantage of libvips v8.3 features.
|
* Take advantage of libvips v8.3 features.
|
||||||
Add support for libvips' new GIF and SVG loaders.
|
Add support for libvips' new GIF and SVG loaders.
|
||||||
Pre-built binaries now include giflib and librsvg, exclude *magick.
|
Pre-built binaries now include giflib and librsvg, exclude *magick.
|
||||||
|
94
index.js
@ -74,6 +74,7 @@ var Sharp = function(input, options) {
|
|||||||
extendLeft: 0,
|
extendLeft: 0,
|
||||||
extendRight: 0,
|
extendRight: 0,
|
||||||
withoutEnlargement: false,
|
withoutEnlargement: false,
|
||||||
|
kernel: 'lanczos3',
|
||||||
interpolator: 'bicubic',
|
interpolator: 'bicubic',
|
||||||
// operations
|
// operations
|
||||||
background: [0, 0, 0, 255],
|
background: [0, 0, 0, 255],
|
||||||
@ -480,33 +481,6 @@ Sharp.prototype.threshold = function(threshold) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
Set the interpolator to use for the affine transformation
|
|
||||||
*/
|
|
||||||
module.exports.interpolator = {
|
|
||||||
nearest: 'nearest',
|
|
||||||
bilinear: 'bilinear',
|
|
||||||
bicubic: 'bicubic',
|
|
||||||
nohalo: 'nohalo',
|
|
||||||
locallyBoundedBicubic: 'lbb',
|
|
||||||
vertexSplitQuadraticBasisSpline: 'vsqbs'
|
|
||||||
};
|
|
||||||
Sharp.prototype.interpolateWith = function(interpolator) {
|
|
||||||
var isValid = false;
|
|
||||||
for (var key in module.exports.interpolator) {
|
|
||||||
if (module.exports.interpolator[key] === interpolator) {
|
|
||||||
isValid = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isValid) {
|
|
||||||
this.options.interpolator = interpolator;
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid interpolator ' + interpolator);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Darken image pre-resize (1/gamma) and brighten post-resize (gamma).
|
Darken image pre-resize (1/gamma) and brighten post-resize (gamma).
|
||||||
Improves brightness of resized image in non-linear colour spaces.
|
Improves brightness of resized image in non-linear colour spaces.
|
||||||
@ -712,27 +686,75 @@ Sharp.prototype.extend = function(extend) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Sharp.prototype.resize = function(width, height) {
|
// Kernels for reduction
|
||||||
if (!width) {
|
module.exports.kernel = {
|
||||||
this.options.width = -1;
|
cubic: 'cubic',
|
||||||
} else {
|
lanczos2: 'lanczos2',
|
||||||
if (typeof width === 'number' && !Number.isNaN(width) && width % 1 === 0 && width > 0 && width <= maximum.width) {
|
lanczos3: 'lanczos3'
|
||||||
|
};
|
||||||
|
// Interpolators for enlargement
|
||||||
|
module.exports.interpolator = {
|
||||||
|
nearest: 'nearest',
|
||||||
|
bilinear: 'bilinear',
|
||||||
|
bicubic: 'bicubic',
|
||||||
|
nohalo: 'nohalo',
|
||||||
|
lbb: 'lbb',
|
||||||
|
locallyBoundedBicubic: 'lbb',
|
||||||
|
vsqbs: 'vsqbs',
|
||||||
|
vertexSplitQuadraticBasisSpline: 'vsqbs'
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Resize image to width x height pixels
|
||||||
|
options.kernel is the kernel to use for reductions, default 'lanczos3'
|
||||||
|
options.interpolator is the interpolator to use for enlargements, default 'bicubic'
|
||||||
|
*/
|
||||||
|
Sharp.prototype.resize = function(width, height, options) {
|
||||||
|
if (isDefined(width)) {
|
||||||
|
if (isInteger(width) && inRange(width, 1, maximum.width)) {
|
||||||
this.options.width = width;
|
this.options.width = width;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid width (1 to ' + maximum.width + ') ' + width);
|
throw new Error('Invalid width (1 to ' + maximum.width + ') ' + width);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!height) {
|
|
||||||
this.options.height = -1;
|
|
||||||
} else {
|
} else {
|
||||||
if (typeof height === 'number' && !Number.isNaN(height) && height % 1 === 0 && height > 0 && height <= maximum.height) {
|
this.options.width = -1;
|
||||||
|
}
|
||||||
|
if (isDefined(height)) {
|
||||||
|
if (isInteger(height) && inRange(height, 1, maximum.height)) {
|
||||||
this.options.height = height;
|
this.options.height = height;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid height (1 to ' + maximum.height + ') ' + height);
|
throw new Error('Invalid height (1 to ' + maximum.height + ') ' + height);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.options.height = -1;
|
||||||
|
}
|
||||||
|
if (isObject(options)) {
|
||||||
|
// Kernel
|
||||||
|
if (isDefined(options.kernel)) {
|
||||||
|
if (isString(module.exports.kernel[options.kernel])) {
|
||||||
|
this.options.kernel = module.exports.kernel[options.kernel];
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid kernel ' + options.kernel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Interpolator
|
||||||
|
if (isDefined(options.interpolator)) {
|
||||||
|
if (isString(module.exports.interpolator[options.interpolator])) {
|
||||||
|
this.options.interpolator = module.exports.interpolator[options.interpolator];
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid interpolator ' + options.interpolator);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
Sharp.prototype.interpolateWith = util.deprecate(function(interpolator) {
|
||||||
|
return this.resize(
|
||||||
|
this.options.width > 0 ? this.options.width : null,
|
||||||
|
this.options.height > 0 ? this.options.height : null,
|
||||||
|
{ interpolator: interpolator }
|
||||||
|
);
|
||||||
|
}, 'interpolateWith: Please use resize(w, h, { interpolator: ... }) instead');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Limit the total number of pixels for input images
|
Limit the total number of pixels for input images
|
||||||
|
@ -250,4 +250,22 @@ namespace sharp {
|
|||||||
return image.hist_find().hist_entropy();
|
return image.hist_find().hist_entropy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Insert a tile cache to prevent over-computation of any previous operations in the pipeline
|
||||||
|
*/
|
||||||
|
VImage TileCache(VImage image, double const factor) {
|
||||||
|
int tile_width;
|
||||||
|
int tile_height;
|
||||||
|
int scanline_count;
|
||||||
|
vips_get_tile_size(image.get_image(), &tile_width, &tile_height, &scanline_count);
|
||||||
|
double const need_lines = 1.2 * scanline_count / factor;
|
||||||
|
return image.tilecache(VImage::option()
|
||||||
|
->set("tile_width", image.width())
|
||||||
|
->set("tile_height", 10)
|
||||||
|
->set("max_tiles", static_cast<int>(round(1.0 + need_lines / 10.0)))
|
||||||
|
->set("access", VIPS_ACCESS_SEQUENTIAL)
|
||||||
|
->set("threaded", TRUE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sharp
|
} // namespace sharp
|
||||||
|
@ -44,6 +44,11 @@ namespace sharp {
|
|||||||
*/
|
*/
|
||||||
double Entropy(VImage image);
|
double Entropy(VImage image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Insert a tile cache to prevent over-computation of any previous operations in the pipeline
|
||||||
|
*/
|
||||||
|
VImage TileCache(VImage image, double const factor);
|
||||||
|
|
||||||
} // namespace sharp
|
} // namespace sharp
|
||||||
|
|
||||||
#endif // SRC_OPERATIONS_H_
|
#endif // SRC_OPERATIONS_H_
|
||||||
|
105
src/pipeline.cc
@ -50,6 +50,7 @@ using sharp::Gamma;
|
|||||||
using sharp::Blur;
|
using sharp::Blur;
|
||||||
using sharp::Sharpen;
|
using sharp::Sharpen;
|
||||||
using sharp::EntropyCrop;
|
using sharp::EntropyCrop;
|
||||||
|
using sharp::TileCache;
|
||||||
|
|
||||||
using sharp::ImageType;
|
using sharp::ImageType;
|
||||||
using sharp::ImageTypeId;
|
using sharp::ImageTypeId;
|
||||||
@ -215,17 +216,13 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
std::swap(inputWidth, inputHeight);
|
std::swap(inputWidth, inputHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get window size of interpolator, used for determining shrink vs affine
|
|
||||||
VInterpolate interpolator = VInterpolate::new_from_name(baton->interpolator.data());
|
|
||||||
int interpolatorWindowSize = vips_interpolate_get_window_size(interpolator.get_interpolate());
|
|
||||||
|
|
||||||
// Scaling calculations
|
// Scaling calculations
|
||||||
double xfactor = 1.0;
|
double xfactor = 1.0;
|
||||||
double yfactor = 1.0;
|
double yfactor = 1.0;
|
||||||
if (baton->width > 0 && baton->height > 0) {
|
if (baton->width > 0 && baton->height > 0) {
|
||||||
// Fixed width and height
|
// Fixed width and height
|
||||||
xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
|
xfactor = static_cast<double>(inputWidth) / (static_cast<double>(baton->width) + 0.1);
|
||||||
yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
|
yfactor = static_cast<double>(inputHeight) / (static_cast<double>(baton->height) + 0.1);
|
||||||
switch (baton->canvas) {
|
switch (baton->canvas) {
|
||||||
case Canvas::CROP:
|
case Canvas::CROP:
|
||||||
xfactor = std::min(xfactor, yfactor);
|
xfactor = std::min(xfactor, yfactor);
|
||||||
@ -262,7 +259,7 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
} else if (baton->width > 0) {
|
} else if (baton->width > 0) {
|
||||||
// Fixed width
|
// Fixed width
|
||||||
xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
|
xfactor = static_cast<double>(inputWidth) / (static_cast<double>(baton->width) + 0.1);
|
||||||
if (baton->canvas == Canvas::IGNORE_ASPECT) {
|
if (baton->canvas == Canvas::IGNORE_ASPECT) {
|
||||||
baton->height = inputHeight;
|
baton->height = inputHeight;
|
||||||
} else {
|
} else {
|
||||||
@ -272,7 +269,7 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
} else if (baton->height > 0) {
|
} else if (baton->height > 0) {
|
||||||
// Fixed height
|
// Fixed height
|
||||||
yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
|
yfactor = static_cast<double>(inputHeight) / (static_cast<double>(baton->height) + 0.1);
|
||||||
if (baton->canvas == Canvas::IGNORE_ASPECT) {
|
if (baton->canvas == Canvas::IGNORE_ASPECT) {
|
||||||
baton->width = inputWidth;
|
baton->width = inputWidth;
|
||||||
} else {
|
} else {
|
||||||
@ -287,12 +284,12 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate integral box shrink
|
// Calculate integral box shrink
|
||||||
int xshrink = CalculateShrink(xfactor, interpolatorWindowSize);
|
int xshrink = std::max(1, static_cast<int>(floor(xfactor)));
|
||||||
int yshrink = CalculateShrink(yfactor, interpolatorWindowSize);
|
int yshrink = std::max(1, static_cast<int>(floor(yfactor)));
|
||||||
|
|
||||||
// Calculate residual float affine transformation
|
// Calculate residual float affine transformation
|
||||||
double xresidual = CalculateResidual(xshrink, xfactor);
|
double xresidual = static_cast<double>(xshrink) / xfactor;
|
||||||
double yresidual = CalculateResidual(yshrink, yfactor);
|
double yresidual = static_cast<double>(yshrink) / yfactor;
|
||||||
|
|
||||||
// Do not enlarge the output if the input width *or* height
|
// Do not enlarge the output if the input width *or* height
|
||||||
// are already less than the required dimensions
|
// are already less than the required dimensions
|
||||||
@ -335,10 +332,10 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
// Recalculate integral shrink and double residual
|
// Recalculate integral shrink and double residual
|
||||||
xfactor = std::max(xfactor, 1.0);
|
xfactor = std::max(xfactor, 1.0);
|
||||||
yfactor = std::max(yfactor, 1.0);
|
yfactor = std::max(yfactor, 1.0);
|
||||||
xshrink = CalculateShrink(xfactor, interpolatorWindowSize);
|
xshrink = std::max(1, static_cast<int>(floor(xfactor)));
|
||||||
yshrink = CalculateShrink(yfactor, interpolatorWindowSize);
|
yshrink = std::max(1, static_cast<int>(floor(yfactor)));
|
||||||
xresidual = CalculateResidual(xshrink, xfactor);
|
xresidual = static_cast<double>(xshrink) / xfactor;
|
||||||
yresidual = CalculateResidual(yshrink, yfactor);
|
yresidual = static_cast<double>(yshrink) / yfactor;
|
||||||
// Reload input using shrink-on-load
|
// Reload input using shrink-on-load
|
||||||
VOption *option = VImage::option()->set("shrink", shrink_on_load);
|
VOption *option = VImage::option()->set("shrink", shrink_on_load);
|
||||||
if (baton->bufferInLength > 1) {
|
if (baton->bufferInLength > 1) {
|
||||||
@ -418,7 +415,12 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (xshrink > 1 || yshrink > 1) {
|
if (xshrink > 1 || yshrink > 1) {
|
||||||
image = image.shrink(xshrink, yshrink);
|
if (yshrink > 1) {
|
||||||
|
image = image.shrinkv(yshrink);
|
||||||
|
}
|
||||||
|
if (xshrink > 1) {
|
||||||
|
image = image.shrinkh(xshrink);
|
||||||
|
}
|
||||||
// Recalculate residual float based on dimensions of required vs shrunk images
|
// Recalculate residual float based on dimensions of required vs shrunk images
|
||||||
int shrunkWidth = image.width();
|
int shrunkWidth = image.width();
|
||||||
int shrunkHeight = image.height();
|
int shrunkHeight = image.height();
|
||||||
@ -466,32 +468,42 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
image = image.premultiply(VImage::option()->set("max_alpha", maxAlpha));
|
image = image.premultiply(VImage::option()->set("max_alpha", maxAlpha));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use affine transformation with the remaining float part
|
// Use affine increase or kernel reduce with the remaining float part
|
||||||
if (shouldAffineTransform) {
|
if (shouldAffineTransform) {
|
||||||
// Use average of x and y residuals to compute sigma for Gaussian blur
|
// Insert tile cache to prevent over-computation of previous operations
|
||||||
double residual = (xresidual + yresidual) / 2.0;
|
|
||||||
// Apply Gaussian blur before large affine reductions
|
|
||||||
if (residual < 1.0) {
|
|
||||||
// Calculate standard deviation
|
|
||||||
double sigma = ((1.0 / residual) - 0.4) / 3.0;
|
|
||||||
if (sigma >= 0.3) {
|
|
||||||
// Sequential input requires a small linecache before use of convolution
|
|
||||||
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL) {
|
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL) {
|
||||||
image = image.linecache(VImage::option()
|
image = TileCache(image, yresidual);
|
||||||
->set("access", VIPS_ACCESS_SEQUENTIAL)
|
}
|
||||||
->set("tile_height", 1)
|
// Perform kernel-based reduction
|
||||||
->set("threaded", TRUE)
|
if (yresidual < 1.0 || xresidual < 1.0) {
|
||||||
|
VipsKernel kernel = static_cast<VipsKernel>(
|
||||||
|
vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data())
|
||||||
);
|
);
|
||||||
|
if (kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 && kernel != VIPS_KERNEL_LANCZOS3) {
|
||||||
|
throw VError("Unknown kernel");
|
||||||
}
|
}
|
||||||
// Apply Gaussian blur
|
if (yresidual < 1.0) {
|
||||||
image = image.gaussblur(sigma);
|
image = image.reducev(1.0 / yresidual, VImage::option()->set("kernel", kernel));
|
||||||
|
}
|
||||||
|
if (xresidual < 1.0) {
|
||||||
|
image = image.reduceh(1.0 / xresidual, VImage::option()->set("kernel", kernel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Perform affine transformation
|
// Perform affine enlargement
|
||||||
image = image.affine({xresidual, 0.0, 0.0, yresidual}, VImage::option()
|
if (yresidual > 1.0 || xresidual > 1.0) {
|
||||||
|
VInterpolate interpolator = 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)
|
->set("interpolate", interpolator)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (xresidual > 1.0) {
|
||||||
|
image = image.affine({xresidual, 0.0, 0.0, 1.0}, VImage::option()
|
||||||
|
->set("interpolate", interpolator)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Rotate
|
// Rotate
|
||||||
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
|
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
|
||||||
@ -943,30 +955,6 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
return std::make_tuple(rotate, flip, flop);
|
return std::make_tuple(rotate, flip, flop);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Calculate integral shrink given factor and interpolator window size
|
|
||||||
*/
|
|
||||||
int CalculateShrink(double factor, int interpolatorWindowSize) {
|
|
||||||
int shrink = 1;
|
|
||||||
if (factor >= 2.0 && trunc(factor) != factor && interpolatorWindowSize > 3) {
|
|
||||||
// Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic
|
|
||||||
shrink = static_cast<int>(floor(factor * 3.0 / interpolatorWindowSize));
|
|
||||||
} else {
|
|
||||||
shrink = static_cast<int>(floor(factor));
|
|
||||||
}
|
|
||||||
if (shrink < 1) {
|
|
||||||
shrink = 1;
|
|
||||||
}
|
|
||||||
return shrink;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Calculate residual given shrink and factor
|
|
||||||
*/
|
|
||||||
double CalculateResidual(int shrink, double factor) {
|
|
||||||
return static_cast<double>(shrink) / factor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Clear all thread-local data.
|
Clear all thread-local data.
|
||||||
*/
|
*/
|
||||||
@ -1058,6 +1046,7 @@ NAN_METHOD(pipeline) {
|
|||||||
// Resize options
|
// Resize options
|
||||||
baton->withoutEnlargement = attrAs<bool>(options, "withoutEnlargement");
|
baton->withoutEnlargement = attrAs<bool>(options, "withoutEnlargement");
|
||||||
baton->crop = attrAs<int32_t>(options, "crop");
|
baton->crop = attrAs<int32_t>(options, "crop");
|
||||||
|
baton->kernel = attrAsStr(options, "kernel");
|
||||||
baton->interpolator = attrAsStr(options, "interpolator");
|
baton->interpolator = attrAsStr(options, "interpolator");
|
||||||
// Operators
|
// Operators
|
||||||
baton->flatten = attrAs<bool>(options, "flatten");
|
baton->flatten = attrAs<bool>(options, "flatten");
|
||||||
|
@ -46,6 +46,7 @@ struct PipelineBaton {
|
|||||||
int channels;
|
int channels;
|
||||||
Canvas canvas;
|
Canvas canvas;
|
||||||
int crop;
|
int crop;
|
||||||
|
std::string kernel;
|
||||||
std::string interpolator;
|
std::string interpolator;
|
||||||
double background[4];
|
double background[4];
|
||||||
bool flatten;
|
bool flatten;
|
||||||
|
@ -10,10 +10,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"async": "^1.5.2",
|
"async": "^1.5.2",
|
||||||
"benchmark": "^2.1.0",
|
"benchmark": "^2.1.0",
|
||||||
"gm": "^1.21.0",
|
"gm": "^1.22.0",
|
||||||
"imagemagick": "^0.1.3",
|
"imagemagick": "^0.1.3",
|
||||||
"imagemagick-native": "^1.9.2",
|
"imagemagick-native": "^1.9.2",
|
||||||
"jimp": "^0.2.20",
|
"jimp": "^0.2.24",
|
||||||
"lwip": "^0.0.8",
|
"lwip": "^0.0.8",
|
||||||
"semver": "^5.1.0"
|
"semver": "^5.1.0"
|
||||||
},
|
},
|
||||||
|
@ -30,18 +30,15 @@ var fixtures = require('../fixtures');
|
|||||||
var width = 720;
|
var width = 720;
|
||||||
var height = 480;
|
var height = 480;
|
||||||
|
|
||||||
var magickFilterBilinear = 'Triangle';
|
|
||||||
var magickFilterBicubic = 'Lanczos';
|
|
||||||
|
|
||||||
// Disable libvips cache to ensure tests are as fair as they can be
|
// Disable libvips cache to ensure tests are as fair as they can be
|
||||||
sharp.cache(false);
|
sharp.cache(false);
|
||||||
// Enable use of SIMD
|
// Enable use of SIMD
|
||||||
sharp.simd(true);
|
sharp.simd(true);
|
||||||
|
|
||||||
async.series({
|
async.series({
|
||||||
'jpeg-linear': function(callback) {
|
'jpeg': function(callback) {
|
||||||
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
|
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
|
||||||
var jpegSuite = new Benchmark.Suite('jpeg-linear');
|
var jpegSuite = new Benchmark.Suite('jpeg');
|
||||||
// jimp
|
// jimp
|
||||||
jpegSuite.add('jimp-buffer-buffer', {
|
jpegSuite.add('jimp-buffer-buffer', {
|
||||||
defer: true,
|
defer: true,
|
||||||
@ -93,7 +90,7 @@ async.series({
|
|||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
image.resize(width, height, 'linear', function (err, image) {
|
image.resize(width, height, 'lanczos', function (err, image) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@ -113,7 +110,7 @@ async.series({
|
|||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
image.resize(width, height, 'linear', function (err, image) {
|
image.resize(width, height, 'lanczos', function (err, image) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@ -140,7 +137,7 @@ async.series({
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
format: 'jpg',
|
format: 'jpg',
|
||||||
filter: magickFilterBilinear
|
filter: 'Lanczos'
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -161,7 +158,7 @@ async.series({
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
format: 'JPEG',
|
format: 'JPEG',
|
||||||
filter: magickFilterBilinear
|
filter: 'Lanczos'
|
||||||
}, function (err, buffer) {
|
}, function (err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -179,7 +176,7 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
gm(inputJpgBuffer)
|
gm(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.filter(magickFilterBilinear)
|
.filter('Lanczos')
|
||||||
.quality(80)
|
.quality(80)
|
||||||
.write(fixtures.outputJpg, function (err) {
|
.write(fixtures.outputJpg, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -194,7 +191,7 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
gm(inputJpgBuffer)
|
gm(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.filter(magickFilterBilinear)
|
.filter('Lanczos')
|
||||||
.quality(80)
|
.quality(80)
|
||||||
.toBuffer(function (err, buffer) {
|
.toBuffer(function (err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -210,7 +207,7 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
gm(fixtures.inputJpg)
|
gm(fixtures.inputJpg)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.filter(magickFilterBilinear)
|
.filter('Lanczos')
|
||||||
.quality(80)
|
.quality(80)
|
||||||
.write(fixtures.outputJpg, function (err) {
|
.write(fixtures.outputJpg, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -225,7 +222,7 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
gm(fixtures.inputJpg)
|
gm(fixtures.inputJpg)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.filter(magickFilterBilinear)
|
.filter('Lanczos')
|
||||||
.quality(80)
|
.quality(80)
|
||||||
.toBuffer(function (err, buffer) {
|
.toBuffer(function (err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -243,7 +240,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toFile(fixtures.outputJpg, function(err) {
|
.toFile(fixtures.outputJpg, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -257,7 +253,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -272,7 +267,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toFile(fixtures.outputJpg, function(err) {
|
.toFile(fixtures.outputJpg, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -290,8 +284,7 @@ async.series({
|
|||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
});
|
});
|
||||||
var pipeline = sharp()
|
var pipeline = sharp()
|
||||||
.resize(width, height)
|
.resize(width, height);
|
||||||
.interpolateWith(sharp.interpolator.bilinear);
|
|
||||||
readable.pipe(pipeline).pipe(writable);
|
readable.pipe(pipeline).pipe(writable);
|
||||||
}
|
}
|
||||||
}).add('sharp-file-buffer', {
|
}).add('sharp-file-buffer', {
|
||||||
@ -299,7 +292,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -314,19 +306,27 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toBuffer()
|
.toBuffer()
|
||||||
.then(function(buffer) {
|
.then(function(buffer) {
|
||||||
assert.notStrictEqual(null, buffer);
|
assert.notStrictEqual(null, buffer);
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add('sharp-sharpen-mild', {
|
}).on('cycle', function(event) {
|
||||||
|
console.log('jpeg ' + String(event.target));
|
||||||
|
}).on('complete', function() {
|
||||||
|
callback(null, this.filter('fastest').map('name'));
|
||||||
|
}).run();
|
||||||
|
},
|
||||||
|
// Effect of applying operations
|
||||||
|
operations: function(callback) {
|
||||||
|
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
|
||||||
|
var operationsSuite = new Benchmark.Suite('operations');
|
||||||
|
operationsSuite.add('sharp-sharpen-mild', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.sharpen()
|
.sharpen()
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -342,7 +342,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.sharpen(3, 1, 3)
|
.sharpen(3, 1, 3)
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -358,7 +357,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.blur()
|
.blur()
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -374,7 +372,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.blur(3)
|
.blur(3)
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -390,7 +387,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.gamma()
|
.gamma()
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -406,7 +402,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.normalise()
|
.normalise()
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -422,7 +417,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.greyscale()
|
.greyscale()
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -438,7 +432,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.gamma()
|
.gamma()
|
||||||
.greyscale()
|
.greyscale()
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
@ -455,7 +448,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.progressive()
|
.progressive()
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -471,7 +463,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.withoutChromaSubsampling()
|
.withoutChromaSubsampling()
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -487,7 +478,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.rotate(90)
|
.rotate(90)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -503,8 +493,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp.simd(false);
|
sharp.simd(false);
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.rotate(90)
|
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
sharp.simd(true);
|
sharp.simd(true);
|
||||||
@ -520,9 +508,8 @@ async.series({
|
|||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.sequentialRead()
|
.sequentialRead()
|
||||||
|
.resize(width, height)
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -533,127 +520,19 @@ async.series({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).on('cycle', function(event) {
|
}).on('cycle', function(event) {
|
||||||
console.log('jpeg-linear ' + String(event.target));
|
console.log('operations ' + String(event.target));
|
||||||
}).on('complete', function() {
|
}).on('complete', function() {
|
||||||
callback(null, this.filter('fastest').map('name'));
|
callback(null, this.filter('fastest').map('name'));
|
||||||
}).run();
|
}).run();
|
||||||
},
|
},
|
||||||
|
// Comparitive speed of kernels
|
||||||
'jpeg-cubic': function(callback) {
|
kernels: function(callback) {
|
||||||
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
|
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
|
||||||
var jpegSuite = new Benchmark.Suite('jpeg-cubic');
|
(new Benchmark.Suite('kernels')).add('sharp-cubic', {
|
||||||
// lwip
|
|
||||||
if (typeof lwip !== 'undefined') {
|
|
||||||
jpegSuite.add('lwip-file-file', {
|
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
lwip.open(fixtures.inputJpg, function (err, image) {
|
sharp(inputJpgBuffer)
|
||||||
if (err) {
|
.resize(width, height, { kernel: 'cubic' })
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
image.resize(width, height, 'lanczos', function (err, image) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
image.writeFile(fixtures.outputJpg, {quality: 80}, function (err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
deferred.resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add('lwip-buffer-buffer', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
lwip.open(inputJpgBuffer, 'jpg', function (err, image) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
image.resize(width, height, 'lanczos', function (err, image) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
image.toBuffer('jpg', {quality: 80}, function (err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// imagemagick
|
|
||||||
jpegSuite.add('imagemagick-file-file', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
imagemagick.resize({
|
|
||||||
srcPath: fixtures.inputJpg,
|
|
||||||
dstPath: fixtures.outputJpg,
|
|
||||||
quality: 0.8,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
format: 'jpg',
|
|
||||||
filter: magickFilterBicubic
|
|
||||||
}, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// imagemagick-native
|
|
||||||
if (typeof imagemagickNative !== 'undefined') {
|
|
||||||
jpegSuite.add('imagemagick-native-buffer-buffer', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
imagemagickNative.convert({
|
|
||||||
srcData: inputJpgBuffer,
|
|
||||||
quality: 80,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
format: 'JPEG',
|
|
||||||
filter: magickFilterBicubic
|
|
||||||
}, function (err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// gm
|
|
||||||
jpegSuite.add('gm-buffer-file', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
gm(inputJpgBuffer)
|
|
||||||
.resize(width, height)
|
|
||||||
.filter(magickFilterBicubic)
|
|
||||||
.quality(80)
|
|
||||||
.write(fixtures.outputJpg, function (err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add('gm-buffer-buffer', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
gm(inputJpgBuffer)
|
|
||||||
.resize(width, height)
|
|
||||||
.filter(magickFilterBicubic)
|
|
||||||
.quality(80)
|
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -663,28 +542,11 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add('gm-file-file', {
|
}).add('sharp-lanczos2', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
gm(fixtures.inputJpg)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height, { kernel: 'lanczos2' })
|
||||||
.filter(magickFilterBicubic)
|
|
||||||
.quality(80)
|
|
||||||
.write(fixtures.outputJpg, function (err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add('gm-file-buffer', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
gm(fixtures.inputJpg)
|
|
||||||
.resize(width, height)
|
|
||||||
.filter(magickFilterBicubic)
|
|
||||||
.quality(80)
|
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -694,182 +556,11 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}).add('sharp-lanczos3', {
|
||||||
// sharp
|
|
||||||
jpegSuite.add('sharp-buffer-file', {
|
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer)
|
sharp(inputJpgBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height, { kernel: 'lanczos3' })
|
||||||
.interpolateWith(sharp.interpolator.bicubic)
|
|
||||||
.toFile(fixtures.outputJpg, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add('sharp-buffer-buffer', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpgBuffer)
|
|
||||||
.resize(width, height)
|
|
||||||
.interpolateWith(sharp.interpolator.bicubic)
|
|
||||||
.toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add('sharp-file-file', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(width, height)
|
|
||||||
.interpolateWith(sharp.interpolator.bicubic)
|
|
||||||
.toFile(fixtures.outputJpg, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add('sharp-stream-stream', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
var readable = fs.createReadStream(fixtures.inputJpg);
|
|
||||||
var writable = fs.createWriteStream(fixtures.outputJpg);
|
|
||||||
writable.on('finish', function() {
|
|
||||||
deferred.resolve();
|
|
||||||
});
|
|
||||||
var pipeline = sharp()
|
|
||||||
.resize(width, height)
|
|
||||||
.interpolateWith(sharp.interpolator.bicubic);
|
|
||||||
readable.pipe(pipeline).pipe(writable);
|
|
||||||
}
|
|
||||||
}).add('sharp-file-buffer', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(width, height)
|
|
||||||
.interpolateWith(sharp.interpolator.bicubic)
|
|
||||||
.toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add('sharp-promise', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpgBuffer)
|
|
||||||
.resize(width, height)
|
|
||||||
.interpolateWith(sharp.interpolator.bicubic)
|
|
||||||
.toBuffer()
|
|
||||||
.then(function(buffer) {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).on('cycle', function(event) {
|
|
||||||
console.log('jpeg-cubic ' + String(event.target));
|
|
||||||
}).on('complete', function() {
|
|
||||||
callback(null, this.filter('fastest').map('name'));
|
|
||||||
}).run();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Comparitive speed of pixel interpolators
|
|
||||||
interpolators: function(callback) {
|
|
||||||
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
|
|
||||||
(new Benchmark.Suite('interpolators')).add('sharp-nearest-neighbour', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpgBuffer)
|
|
||||||
.resize(width, height)
|
|
||||||
.interpolateWith(sharp.interpolator.nearest)
|
|
||||||
.toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add('sharp-bilinear', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpgBuffer)
|
|
||||||
.resize(width, height)
|
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add('sharp-vertexSplitQuadraticBasisSpline', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpgBuffer)
|
|
||||||
.resize(width, height)
|
|
||||||
.interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline)
|
|
||||||
.toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add('sharp-bicubic', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpgBuffer)
|
|
||||||
.resize(width, height)
|
|
||||||
.interpolateWith(sharp.interpolator.bicubic)
|
|
||||||
.toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add('sharp-locallyBoundedBicubic', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpgBuffer)
|
|
||||||
.resize(width, height)
|
|
||||||
.interpolateWith(sharp.interpolator.locallyBoundedBicubic)
|
|
||||||
.toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add('sharp-nohalo', {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpgBuffer)
|
|
||||||
.resize(width, height)
|
|
||||||
.interpolateWith(sharp.interpolator.nohalo)
|
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -880,12 +571,12 @@ async.series({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).on('cycle', function(event) {
|
}).on('cycle', function(event) {
|
||||||
console.log('interpolators ' + String(event.target));
|
console.log('kernels ' + String(event.target));
|
||||||
}).on('complete', function() {
|
}).on('complete', function() {
|
||||||
callback(null, this.filter('fastest').map('name'));
|
callback(null, this.filter('fastest').map('name'));
|
||||||
}).run();
|
}).run();
|
||||||
},
|
},
|
||||||
|
// PNG
|
||||||
png: function(callback) {
|
png: function(callback) {
|
||||||
var inputPngBuffer = fs.readFileSync(fixtures.inputPng);
|
var inputPngBuffer = fs.readFileSync(fixtures.inputPng);
|
||||||
var pngSuite = new Benchmark.Suite('png');
|
var pngSuite = new Benchmark.Suite('png');
|
||||||
@ -938,7 +629,7 @@ async.series({
|
|||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
image.resize(width, height, 'linear', function (err, image) {
|
image.resize(width, height, 'lanczos', function (err, image) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@ -963,7 +654,7 @@ async.series({
|
|||||||
dstPath: fixtures.outputPng,
|
dstPath: fixtures.outputPng,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
filter: magickFilterBilinear
|
filter: 'Lanczos'
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -983,7 +674,7 @@ async.series({
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
format: 'PNG',
|
format: 'PNG',
|
||||||
filter: magickFilterBilinear
|
filter: 'Lanczos'
|
||||||
});
|
});
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
}
|
}
|
||||||
@ -995,7 +686,7 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
gm(fixtures.inputPng)
|
gm(fixtures.inputPng)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.filter(magickFilterBilinear)
|
.filter('Lanczos')
|
||||||
.write(fixtures.outputPng, function (err) {
|
.write(fixtures.outputPng, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -1009,7 +700,7 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
gm(fixtures.inputPng)
|
gm(fixtures.inputPng)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.filter(magickFilterBilinear)
|
.filter('Lanczos')
|
||||||
.toBuffer(function (err, buffer) {
|
.toBuffer(function (err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -1026,7 +717,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputPngBuffer)
|
sharp(inputPngBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toFile(fixtures.outputPng, function(err) {
|
.toFile(fixtures.outputPng, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -1040,7 +730,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputPngBuffer)
|
sharp(inputPngBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -1055,7 +744,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(fixtures.inputPng)
|
sharp(fixtures.inputPng)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toFile(fixtures.outputPng, function(err) {
|
.toFile(fixtures.outputPng, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -1069,7 +757,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(fixtures.inputPng)
|
sharp(fixtures.inputPng)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -1084,7 +771,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputPngBuffer)
|
sharp(inputPngBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.progressive()
|
.progressive()
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -1100,7 +786,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputPngBuffer)
|
sharp(inputPngBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.withoutAdaptiveFiltering()
|
.withoutAdaptiveFiltering()
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -1118,7 +803,7 @@ async.series({
|
|||||||
callback(null, this.filter('fastest').map('name'));
|
callback(null, this.filter('fastest').map('name'));
|
||||||
}).run();
|
}).run();
|
||||||
},
|
},
|
||||||
|
// WebP
|
||||||
webp: function(callback) {
|
webp: function(callback) {
|
||||||
var inputWebPBuffer = fs.readFileSync(fixtures.inputWebP);
|
var inputWebPBuffer = fs.readFileSync(fixtures.inputWebP);
|
||||||
(new Benchmark.Suite('webp')).add('sharp-buffer-file', {
|
(new Benchmark.Suite('webp')).add('sharp-buffer-file', {
|
||||||
@ -1126,7 +811,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputWebPBuffer)
|
sharp(inputWebPBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toFile(fixtures.outputWebP, function(err) {
|
.toFile(fixtures.outputWebP, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -1140,7 +824,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputWebPBuffer)
|
sharp(inputWebPBuffer)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -1155,7 +838,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(fixtures.inputWebP)
|
sharp(fixtures.inputWebP)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toFile(fixtures.outputWebP, function(err) {
|
.toFile(fixtures.outputWebP, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -1169,7 +851,6 @@ async.series({
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(fixtures.inputWebp)
|
sharp(fixtures.inputWebp)
|
||||||
.resize(width, height)
|
.resize(width, height)
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toBuffer(function(err, buffer) {
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
|
@ -8,14 +8,12 @@ var Benchmark = require('benchmark');
|
|||||||
var sharp = require('../../index');
|
var sharp = require('../../index');
|
||||||
var fixtures = require('../fixtures');
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(false);
|
||||||
sharp.simd(true);
|
sharp.simd(true);
|
||||||
|
|
||||||
var min = 320;
|
var min = 320;
|
||||||
var max = 960;
|
var max = 960;
|
||||||
|
|
||||||
// Nearest equivalent to bilinear
|
|
||||||
var magickFilter = 'Triangle';
|
|
||||||
|
|
||||||
var randomDimension = function() {
|
var randomDimension = function() {
|
||||||
return Math.ceil(Math.random() * (max - min) + min);
|
return Math.ceil(Math.random() * (max - min) + min);
|
||||||
};
|
};
|
||||||
@ -30,7 +28,7 @@ new Benchmark.Suite('random').add('imagemagick', {
|
|||||||
width: randomDimension(),
|
width: randomDimension(),
|
||||||
height: randomDimension(),
|
height: randomDimension(),
|
||||||
format: 'jpg',
|
format: 'jpg',
|
||||||
filter: magickFilter
|
filter: 'Lanczos'
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -44,7 +42,7 @@ new Benchmark.Suite('random').add('imagemagick', {
|
|||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
gm(fixtures.inputJpg)
|
gm(fixtures.inputJpg)
|
||||||
.resize(randomDimension(), randomDimension())
|
.resize(randomDimension(), randomDimension())
|
||||||
.filter(magickFilter)
|
.filter('Lanczos')
|
||||||
.quality(80)
|
.quality(80)
|
||||||
.toBuffer(function (err, buffer) {
|
.toBuffer(function (err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -58,7 +56,9 @@ new Benchmark.Suite('random').add('imagemagick', {
|
|||||||
}).add('sharp', {
|
}).add('sharp', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(fixtures.inputJpg).resize(randomDimension(), randomDimension()).toBuffer(function(err, buffer) {
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(randomDimension(), randomDimension())
|
||||||
|
.toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@ -72,5 +72,4 @@ new Benchmark.Suite('random').add('imagemagick', {
|
|||||||
}).on('complete', function() {
|
}).on('complete', function() {
|
||||||
var winner = this.filter('fastest').map('name');
|
var winner = this.filter('fastest').map('name');
|
||||||
assert.strictEqual('sharp', String(winner), 'sharp was slower than ' + winner);
|
assert.strictEqual('sharp', String(winner), 'sharp was slower than ' + winner);
|
||||||
console.dir(sharp.cache());
|
|
||||||
}).run();
|
}).run();
|
||||||
|
BIN
test/fixtures/expected/crop-entropy.jpg
vendored
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.5 KiB |
BIN
test/fixtures/expected/embed-16bit-rgba.png
vendored
Before Width: | Height: | Size: 782 B After Width: | Height: | Size: 988 B |
BIN
test/fixtures/expected/embed-16bit.png
vendored
Before Width: | Height: | Size: 789 B After Width: | Height: | Size: 999 B |
BIN
test/fixtures/expected/flatten-rgb16-orange.jpg
vendored
Before Width: | Height: | Size: 840 B After Width: | Height: | Size: 839 B |
BIN
test/fixtures/expected/gamma-0.0.jpg
vendored
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 621 B |
BIN
test/fixtures/expected/svg1200.png
vendored
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 716 B |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 15 KiB |
1
test/fixtures/index.js
vendored
@ -18,7 +18,6 @@ var fingerprint = function(image, callback) {
|
|||||||
.normalise()
|
.normalise()
|
||||||
.resize(9, 8)
|
.resize(9, 8)
|
||||||
.ignoreAspectRatio()
|
.ignoreAspectRatio()
|
||||||
.interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline)
|
|
||||||
.raw()
|
.raw()
|
||||||
.toBuffer(function(err, data) {
|
.toBuffer(function(err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
# Interpolators
|
|
||||||
|
|
||||||
[Photo](https://www.flickr.com/photos/aotaro/21978966091) by
|
|
||||||
[aotaro](https://www.flickr.com/photos/aotaro/) is licensed under
|
|
||||||
[CC BY 2.0](https://creativecommons.org/licenses/by/2.0/).
|
|
||||||
|
|
||||||
The following examples take the 4608x3072px original image
|
|
||||||
and resize to 480x320px using various interpolators.
|
|
||||||
|
|
||||||
To fetch the original 4608x3072px image and
|
|
||||||
generate the interpolator sample images:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl -O https://farm6.staticflickr.com/5682/21978966091_b421afe866_o.jpg
|
|
||||||
node generate.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## Nearest neighbour
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Bilinear
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Bicubic
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Locally bounded bicubic
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Vertex-split quadratic b-splines (VSQBS)
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Nohalo
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## GraphicsMagick
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```sh
|
|
||||||
gm convert 21978966091_b421afe866_o.jpg -resize 480x320^ -gravity center -extent 480x320 -quality 95 -strip -define jpeg:optimize-coding=true gm.jpg
|
|
||||||
```
|
|
Before Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 61 KiB |
@ -1,11 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
['nearest', 'bilinear', 'bicubic', 'vsqbs', 'lbb', 'nohalo'].forEach(function(interpolator) {
|
|
||||||
require('../../')('21978966091_b421afe866_o.jpg')
|
|
||||||
.resize(480, 320)
|
|
||||||
.interpolateWith(interpolator)
|
|
||||||
.quality(95)
|
|
||||||
.toFile(interpolator + '.jpg', function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
});
|
|
||||||
});
|
|
Before Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 58 KiB |
@ -45,15 +45,17 @@ describe('Alpha transparency', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Flatten 16-bit PNG with transparency to orange', function(done) {
|
it('Flatten 16-bit PNG with transparency to orange', function(done) {
|
||||||
|
var output = fixtures.path('output.flatten-rgb16-orange.jpg');
|
||||||
sharp(fixtures.inputPngWithTransparency16bit)
|
sharp(fixtures.inputPngWithTransparency16bit)
|
||||||
.flatten()
|
.flatten()
|
||||||
.background({r: 255, g: 102, b: 0})
|
.background({r: 255, g: 102, b: 0})
|
||||||
.toBuffer(function(err, data, info) {
|
.toFile(output, function(err, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(true, info.size > 0);
|
assert.strictEqual(true, info.size > 0);
|
||||||
assert.strictEqual(32, info.width);
|
assert.strictEqual(32, info.width);
|
||||||
assert.strictEqual(32, info.height);
|
assert.strictEqual(32, info.height);
|
||||||
fixtures.assertSimilar(fixtures.expected('flatten-rgb16-orange.jpg'), data, { threshold: 6 }, done);
|
fixtures.assertMaxColourDistance(output, fixtures.expected('flatten-rgb16-orange.jpg'), 25);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ describe('Clone', function() {
|
|||||||
var rotator = sharp().rotate(90);
|
var rotator = sharp().rotate(90);
|
||||||
// Cloned instances with differing dimensions
|
// Cloned instances with differing dimensions
|
||||||
rotator.clone().resize(320, 240).pipe(writable1);
|
rotator.clone().resize(320, 240).pipe(writable1);
|
||||||
rotator.clone().resize(100).pipe(writable2);
|
rotator.clone().resize(100, 122).pipe(writable2);
|
||||||
// Go
|
// Go
|
||||||
fs.createReadStream(fixtures.inputJpg).pipe(rotator);
|
fs.createReadStream(fixtures.inputJpg).pipe(rotator);
|
||||||
});
|
});
|
||||||
|
@ -5,100 +5,67 @@ var assert = require('assert');
|
|||||||
var sharp = require('../../index');
|
var sharp = require('../../index');
|
||||||
var fixtures = require('../fixtures');
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
describe('Interpolation', function() {
|
describe('Interpolators and kernels', function() {
|
||||||
|
|
||||||
it('nearest neighbour', function(done) {
|
describe('Reducers', function() {
|
||||||
|
[
|
||||||
|
sharp.kernel.cubic,
|
||||||
|
sharp.kernel.lanczos2,
|
||||||
|
sharp.kernel.lanczos3
|
||||||
|
].forEach(function(kernel) {
|
||||||
|
it(kernel, function(done) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(320, 240)
|
.resize(320, null, { kernel: kernel })
|
||||||
.interpolateWith(sharp.interpolator.nearest)
|
|
||||||
.toBuffer(function(err, data, info) {
|
.toBuffer(function(err, data, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
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);
|
fixtures.assertSimilar(fixtures.inputJpg, data, done);
|
||||||
done();
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('bilinear', function(done) {
|
describe('Enlargers', function() {
|
||||||
|
[
|
||||||
|
sharp.interpolator.nearest,
|
||||||
|
sharp.interpolator.bilinear,
|
||||||
|
sharp.interpolator.bicubic,
|
||||||
|
sharp.interpolator.nohalo,
|
||||||
|
sharp.interpolator.locallyBoundedBicubic,
|
||||||
|
sharp.interpolator.vertexSplitQuadraticBasisSpline
|
||||||
|
].forEach(function(interpolator) {
|
||||||
|
it(interpolator, function(done) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(320, 240)
|
.resize(320, null, { interpolator: interpolator })
|
||||||
.interpolateWith(sharp.interpolator.bilinear)
|
|
||||||
.toBuffer(function(err, data, info) {
|
.toBuffer(function(err, data, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
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);
|
fixtures.assertSimilar(fixtures.inputJpg, data, done);
|
||||||
done();
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('bicubic', function(done) {
|
it('unknown kernel throws', function() {
|
||||||
sharp(fixtures.inputJpg)
|
assert.throws(function() {
|
||||||
.resize(320, 240)
|
sharp().resize(null, null, { kernel: 'unknown' });
|
||||||
.interpolateWith(sharp.interpolator.bicubic)
|
|
||||||
.toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(240, info.height);
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('nohalo', function(done) {
|
it('unknown interpolator throws', function() {
|
||||||
sharp(fixtures.inputJpg)
|
assert.throws(function() {
|
||||||
.resize(320, 240)
|
sharp().resize(null, null, { interpolator: 'unknown' });
|
||||||
.interpolateWith(sharp.interpolator.nohalo)
|
|
||||||
.toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(240, info.height);
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('locally bounded bicubic (LBB)', function(done) {
|
describe('deprecated interpolateWith method still works', function() {
|
||||||
sharp(fixtures.inputJpg)
|
it('resize then interpolateWith', function() {
|
||||||
.resize(320, 240)
|
sharp().resize(1, 1).interpolateWith('bicubic');
|
||||||
.interpolateWith(sharp.interpolator.locallyBoundedBicubic)
|
});
|
||||||
.toBuffer(function(err, data, info) {
|
it('interpolateWith then resize', function() {
|
||||||
if (err) throw err;
|
sharp().interpolateWith('bicubic').resize(1, 1);
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(240, info.height);
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('vertex split quadratic basis spline (VSQBS)', function(done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(320, 240)
|
|
||||||
.interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline)
|
|
||||||
.toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(240, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('unknown interpolator throws', function(done) {
|
|
||||||
var isValid = false;
|
|
||||||
try {
|
|
||||||
sharp().interpolateWith('nonexistant');
|
|
||||||
isValid = true;
|
|
||||||
} catch (e) {}
|
|
||||||
assert(!isValid);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1028,14 +1028,14 @@ describe('Input/output', function() {
|
|||||||
it('Info event data', function(done) {
|
it('Info event data', function(done) {
|
||||||
var readable = fs.createReadStream(fixtures.inputJPGBig);
|
var readable = fs.createReadStream(fixtures.inputJPGBig);
|
||||||
var inPipeline = sharp()
|
var inPipeline = sharp()
|
||||||
.resize(840)
|
.resize(840, 472)
|
||||||
.raw()
|
.raw()
|
||||||
.on('info', function(info) {
|
.on('info', function(info) {
|
||||||
assert.strictEqual(840, info.width);
|
assert.strictEqual(840, info.width);
|
||||||
assert.strictEqual(472, info.height);
|
assert.strictEqual(472, info.height);
|
||||||
assert.strictEqual(3, info.channels);
|
assert.strictEqual(3, info.channels);
|
||||||
});
|
});
|
||||||
var badPipeline = sharp(null, {raw: {width: 840, height: 473, channels: 3}})
|
var badPipeline = sharp(null, {raw: {width: 840, height: 500, channels: 3}})
|
||||||
.toFormat('jpeg')
|
.toFormat('jpeg')
|
||||||
.toBuffer(function(err, data, info) {
|
.toBuffer(function(err, data, info) {
|
||||||
assert.strictEqual(err.message.indexOf('memory area too small') > 0, true);
|
assert.strictEqual(err.message.indexOf('memory area too small') > 0, true);
|
||||||
@ -1047,7 +1047,7 @@ describe('Input/output', function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
inPipeline = sharp()
|
inPipeline = sharp()
|
||||||
.resize(840)
|
.resize(840, 472)
|
||||||
.raw();
|
.raw();
|
||||||
readable.pipe(inPipeline).pipe(goodPipeline);
|
readable.pipe(inPipeline).pipe(goodPipeline);
|
||||||
});
|
});
|
||||||
|
@ -96,7 +96,7 @@ describe('Threshold', function() {
|
|||||||
.threshold()
|
.threshold()
|
||||||
.toBuffer(function(err, data, info) {
|
.toBuffer(function(err, data, info) {
|
||||||
assert.strictEqual('webp', info.format);
|
assert.strictEqual('webp', info.format);
|
||||||
fixtures.assertSimilar(fixtures.expected('threshold-128-transparency.webp'), data, { threshold: 14 }, done);
|
fixtures.assertSimilar(fixtures.expected('threshold-128-transparency.webp'), data, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|