Compare commits

..

14 Commits

Author SHA1 Message Date
Lovell Fuller
d5d85a8697 Expose vips internal cache settings and status 2014-02-25 23:31:33 +00:00
Lovell Fuller
ae9a8b0f57 Remove unnecessary temporary copy of input buffer. 2014-02-23 10:52:40 +00:00
Lovell Fuller
0899252a72 Add support for WebP and TIFF image formats. Closes #7. 2014-02-22 21:48:00 +00:00
Lovell Fuller
16551bc058 Take a temporary copy of buffers provided as input. Soak testing suggests this prevents the problems seen in #6. 2014-02-12 22:36:13 +00:00
Lovell Fuller
e9d196f696 Fix (unrelated) memory leak discovered whilst investigating #6 2014-02-06 22:20:48 +00:00
Lovell Fuller
2f97d04dfa Keep shrink-on-load logic DRY. Ensure canvas option (crop vs embed) is always as requested. 2014-02-03 23:20:38 +00:00
Lovell Fuller
e4ca8f44ec Version bump 2014-02-01 23:00:37 +00:00
Lovell Fuller
6b3dc1e350 Ensure crop occurs on y-axis 2014-02-01 22:58:30 +00:00
Lovell Fuller
7ffcdb79e0 Add glib-2.0 path for Mac OS X 2014-01-30 18:28:24 +00:00
Lovell Fuller
10ce7c6693 Add parallel performance test to demonstrate effect of waiting for a worker thread 2014-01-26 20:16:42 +00:00
Lovell Fuller
ccd6012152 Version bump 2014-01-21 22:49:40 +00:00
Lovell Fuller
377662fffc Ensure gm perf tests actually resize. Prevent coredump when embeding pre-shrunk image within same dimensions. 2014-01-21 22:48:33 +00:00
Lovell Fuller
d509458ba1 Major rewrite and therefore API change. Despite the name, image sharpening is now optional, plus there are other new options. Can now handle buffers in and out. Doubled performance in certain cases. Closes #3. Closes #4. 2014-01-19 21:38:37 +00:00
Lovell Fuller
be8f35d830 Use shrink-on-load for JPEG images, partially implementing #4. Switch to new vip_ methods from legacy im_ methods. Large performance gains all round. 2014-01-16 22:51:44 +00:00
10 changed files with 774 additions and 245 deletions

3
.gitignore vendored
View File

@@ -12,7 +12,6 @@ logs
results results
build build
node_modules node_modules
tests/output.jpg tests/output.*
tests/output.png
npm-debug.log npm-debug.log

152
README.md
View File

@@ -1,4 +1,4 @@
# sharp # sharp
_adj_ _adj_
@@ -7,24 +7,21 @@ _adj_
3. shrewd or astute: a sharp bargainer. 3. shrewd or astute: a sharp bargainer.
4. (Informal.) very stylish: a sharp dresser; a sharp jacket. 4. (Informal.) very stylish: a sharp dresser; a sharp jacket.
The typical use case for this high speed Node.js module is to convert large JPEG and PNG images to smaller JPEG and PNG images of varying dimensions. The typical use case for this high speed Node.js module is to convert large JPEG, PNG, WebP and TIFF images to smaller images of varying dimensions.
It is somewhat opinionated in that it only deals with JPEG and PNG images, always obeys the requested dimensions by either cropping or embedding and insists on a mild sharpen of the resulting image. The performance of JPEG resizing is typically 15x-25x faster than ImageMagick and GraphicsMagick, based mainly on the number of CPU cores available.
Under the hood you'll find the blazingly fast [libvips](https://github.com/jcupitt/libvips) image processing library, originally created in 1989 at Birkbeck College and currently maintained by the University of Southampton. This module supports reading and writing images to and from both the filesystem and Buffer objects (TIFF is limited to filesystem only). Everything remains non-blocking thanks to _libuv_.
Performance is 4x-8x faster than ImageMagick and 2x-4x faster than GraphicsMagick, based mainly on the number of CPU cores available. Under the hood you'll find the blazingly fast [libvips](https://github.com/jcupitt/libvips) image processing library, originally created in 1989 at Birkbeck College and currently maintained by John Cupitt.
## Prerequisites ## Prerequisites
* Node.js v0.8+ * Node.js v0.8+
* node-gyp * [libvips](https://github.com/jcupitt/libvips) v7.38+
* [libvips](https://github.com/jcupitt/libvips) v7.37+
For the sharpest results, please compile libvips from source. For the sharpest results, please compile libvips from source.
If you prefer to run a stable, package-managed environment such as Ubuntu 12.04 LTS, [v0.0.3](https://github.com/lovell/sharp/tree/v0.0.3) will work with the libvips-dev package.
## Install ## Install
npm install sharp npm install sharp
@@ -33,12 +30,31 @@ If you prefer to run a stable, package-managed environment such as Ubuntu 12.04
var sharp = require("sharp"); var sharp = require("sharp");
### crop(input, output, width, height, callback) ### resize(input, output, width, height, [options], callback)
Scale and crop to `width` x `height` calling `callback` when complete. Scale and crop to `width` x `height` calling `callback` when complete.
`input` can either be a filename String or a Buffer.
`output` can either be a filename String or one of `sharp.buffer.jpeg`, `sharp.buffer.png` or `sharp.buffer.webp` to pass a Buffer containing JPEG, PNG or WebP image data to `callback`.
`width` is the Number of pixels wide the resultant image should be.
`height` is the Number of pixels high the resultant image should be.
`options` is optional, and can contain one or more of:
* `canvas` can be one of `sharp.canvas.crop`, `sharp.canvas.embedWhite` or `sharp.canvas.embedBlack`. Defaults to `sharp.canvas.crop`.
* `sharpen` when set to true will perform a mild sharpen of the resultant image. This typically reduces performance by 30%.
* `progressive` when set will use progressive (interlace) scan for the output. This typically reduces performance by 30%.
* `sequentialRead` is an advanced setting that, when set, switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. This will reduce memory usage and can improve performance on some systems.
`callback` gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant image data when a Buffer is requested.
#### Examples
```javascript ```javascript
sharp.crop("input.jpg", "output.jpg", 300, 200, function(err) { sharp.resize("input.jpg", "output.jpg", 300, 200, function(err) {
if (err) { if (err) {
throw err; throw err;
} }
@@ -48,72 +64,56 @@ sharp.crop("input.jpg", "output.jpg", 300, 200, function(err) {
``` ```
```javascript ```javascript
sharp.crop("input.jpg", sharp.buffer.jpeg, 300, 200, function(err, buffer) { sharp.resize("input.jpg", sharp.buffer.jpeg, 300, 200, {progressive: true}, function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} }
// buffer contains JPEG image data // buffer contains progressive JPEG image data
}); });
``` ```
```javascript ```javascript
sharp.crop("input.jpg", sharp.buffer.png, 300, 200, function(err, buffer) { sharp.resize("input.webp", sharp.buffer.png, 300, 200, {sharpen: true}, function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} }
// buffer contains PNG image data (converted from JPEG) // buffer contains sharpened PNG image data (converted from JPEG)
});
```
### embedWhite(input, output, width, height, callback)
Scale and embed to `width` x `height` using a white canvas calling `callback` when complete.
```javascript
sharp.embedWhite("input.jpg", "output.jpg", 200, 300, function(err) {
if (err) {
throw err;
}
// output.jpg is a 200 pixels wide and 300 pixels high image
// containing a scaled version of input.png embedded on a white canvas
}); });
``` ```
```javascript ```javascript
sharp.embedWhite("input.jpg", sharp.buffer.jpeg, 200, 300, function(err, buffer) { sharp.resize(buffer, "output.tiff", 200, 300, {canvas: sharp.canvas.embedWhite}, function(err) {
if (err) { if (err) {
throw err; throw err;
} }
// buffer contains JPEG image data // output.tiff is a 200 pixels wide and 300 pixels high image containing a scaled version
// of the image data contained in buffer embedded on a white canvas
}); });
``` ```
### embedBlack(input, output, width, height, callback)
Scale and embed to `width` x `height` using a black canvas calling `callback` when complete.
```javascript ```javascript
sharp.embedBlack("input.png", "output.png", 200, 300, function(err) { sharp.resize("input.jpg", sharp.buffer.webp, 200, 300, {canvas: sharp.canvas.embedBlack}, function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} }
// output.png is a 200 pixels wide and 300 pixels high image // buffer contains WebP image data of a 200 pixels wide and 300 pixels high image
// containing a scaled version of input.png embedded on a black canvas // containing a scaled version of input.png embedded on a black canvas
}); });
``` ```
### Parameters common to all methods ### cache([limit])
#### input If `limit` is provided, set the `vips` internal cache limit to this value in MB. The default value is 100.
String containing the filename to read from. Always returns cache statistics, namely current usage, high water mark and maximum limit.
#### output The high water mark may be higher than the maximum limit.
One of: ```javascript
* String containing the filename to write to. var stats = sharp.cache(); // { current: 98, high: 115, limit: 100 }
* `sharp.buffer.jpeg` to pass a Buffer containing JPEG image data to `callback`. sharp.cache(200); // { current: 98, high: 115, limit: 200 }
* `sharp.buffer.png` to pass a Buffer containing PNG image data to `callback`. sharp.cache(50); // { current: 49, high: 115, limit: 50 }
```
## Testing ## Testing
@@ -124,25 +124,63 @@ One of:
Test environment: Test environment:
* AMD Athlon 4 core 3.3GHz 512KB L2 CPU 1333 DDR3 * AMD Athlon 4 core 3.3GHz 512KB L2 CPU 1333 DDR3
* libvips 7.37 * libvips 7.38
* libjpeg-turbo8 1.3.0 * libjpeg-turbo8 1.3.0
* libpng 1.6.6 * libpng 1.6.6
* zlib1g 1.2.7 * zlib1g 1.2.7
* libwebp 0.3.0
* libtiff 4.0.2
#### JPEG `-file-buffer` indicates read from file and write to buffer, `-buffer-file` indicates read from buffer and write to file etc.
* imagemagick x 5.53 ops/sec ±0.55% (31 runs sampled) `-sharpen`, `-progressive` etc. demonstrate the negative effect of options on performance.
* gm x 10.86 ops/sec ±0.43% (56 runs sampled)
* epeg x 28.07 ops/sec ±0.07% (70 runs sampled)
* sharp-file x 31.60 ops/sec ±8.80% (80 runs sampled)
* sharp-buffer x 34.04 ops/sec ±0.36% (82 runs sampled)
#### PNG ### JPEG
* imagemagick x 4.65 ops/sec ±0.37% (27 runs sampled) * imagemagick x 5.53 ops/sec ±0.62% (31 runs sampled)
* gm x 21.65 ops/sec ±0.18% (56 runs sampled) * gm-file-file x 4.10 ops/sec ±0.41% (25 runs sampled)
* sharp-file x 39.47 ops/sec ±6.78% (68 runs sampled) * gm-file-buffer x 4.10 ops/sec ±0.36% (25 runs sampled)
* sharp-buffer x 42.87 ops/sec ±0.19% (71 runs sampled) * epeg-file-file x 23.82 ops/sec ±0.18% (60 runs sampled)
* epeg-file-buffer x 23.98 ops/sec ±0.16% (61 runs sampled)
* sharp-buffer-file x 20.76 ops/sec ±0.55% (54 runs sampled)
* sharp-buffer-buffer x 20.90 ops/sec ±0.26% (54 runs sampled)
* sharp-file-file x 91.78 ops/sec ±0.38% (88 runs sampled)
* sharp-file-buffer x __93.05 ops/sec__ ±0.61% (76 runs sampled)
* sharp-file-buffer-sharpen x 63.09 ops/sec ±5.58% (63 runs sampled)
* sharp-file-buffer-progressive x 61.68 ops/sec ±0.53% (76 runs sampled)
* sharp-file-buffer-sequentialRead x 60.66 ops/sec ±0.38% (75 runs sampled)
### PNG
* imagemagick x 4.27 ops/sec ±0.21% (25 runs sampled)
* gm-file-file x 8.33 ops/sec ±0.19% (44 runs sampled)
* gm-file-buffer x 7.45 ops/sec ±0.16% (40 runs sampled)
* sharp-buffer-file x 4.94 ops/sec ±118.46% (26 runs sampled)
* sharp-buffer-buffer x 12.59 ops/sec ±0.55% (64 runs sampled)
* sharp-file-file x 44.06 ops/sec ±6.86% (75 runs sampled)
* sharp-file-buffer x __46.29 ops/sec__ ±0.38% (76 runs sampled)
* sharp-file-buffer-sharpen x 38.86 ops/sec ±0.22% (65 runs sampled)
* sharp-file-buffer-progressive x 46.35 ops/sec ±0.20% (76 runs sampled)
* sharp-file-buffer-sequentialRead x 29.02 ops/sec ±0.62% (72 runs sampled)
### WebP
* sharp-buffer-file x 3.30 ops/sec ±117.14% (19 runs sampled)
* sharp-buffer-buffer x 7.66 ops/sec ±5.83% (43 runs sampled)
* sharp-file-file x 9.88 ops/sec ±0.98% (52 runs sampled)
* sharp-file-buffer x 9.95 ops/sec ±0.25% (52 runs sampled)
* sharp-file-buffer-sharpen x 9.05 ops/sec ±0.36% (48 runs sampled)
* sharp-file-buffer-sequentialRead x 9.87 ops/sec ±0.98% (52 runs sampled)
### TIFF
* sharp-file-file x 68.24 ops/sec ±5.93% (85 runs sampled)
* sharp-file-file-sharpen x 50.76 ops/sec ±0.52% (82 runs sampled)
* sharp-file-file-sequentialRead x 36.37 ops/sec ±0.90% (87 runs sampled)
## Licence ## Licence

View File

@@ -7,11 +7,13 @@
'<!@(PKG_CONFIG_PATH="/usr/lib/pkgconfig" pkg-config --libs vips)' '<!@(PKG_CONFIG_PATH="/usr/lib/pkgconfig" pkg-config --libs vips)'
], ],
'include_dirs': [ 'include_dirs': [
'/usr/local/include/glib-2.0',
'/usr/local/lib/glib-2.0/include',
'/usr/include/glib-2.0', '/usr/include/glib-2.0',
'/usr/lib/glib-2.0/include', '/usr/lib/glib-2.0/include',
'/usr/lib/x86_64-linux-gnu/glib-2.0/include' '/usr/lib/x86_64-linux-gnu/glib-2.0/include'
], ],
'cflags': ['-fexceptions'], 'cflags': ['-fexceptions', '-pedantic', '-Wall', '-O3'],
'cflags_cc': ['-fexceptions'] 'cflags_cc': ['-fexceptions', '-pedantic', '-Wall', '-O3']
}] }]
} }

View File

@@ -2,17 +2,71 @@ var sharp = require("./build/Release/sharp");
module.exports.buffer = { module.exports.buffer = {
jpeg: "__jpeg", jpeg: "__jpeg",
png: "__png" png: "__png",
webp: "__webp"
}; };
module.exports.crop = function(input, output, width, height, callback) { module.exports.canvas = {
sharp.resize(input, output, width, height, "c", callback); crop: "c",
embedWhite: "w",
embedBlack: "b"
}; };
module.exports.resize = function(input, output, width, height, options, callback) {
"use strict";
if (typeof options === 'function') {
callback = options;
options = {};
} else {
options = options || {};
}
if (typeof input === 'string') {
options.inFile = input;
} else if (typeof input ==='object' && input instanceof Buffer) {
options.inBuffer = input;
} else {
callback("Unsupported input " + typeof input);
return;
}
if (!output || output.length === 0) {
callback("Invalid output");
return;
}
var outWidth = Number(width);
if (Number.isNaN(outWidth)) {
callback("Invalid width " + width);
return;
}
var outHeight = Number(height);
if (Number.isNaN(outHeight)) {
callback("Invalid height " + height);
return;
}
var canvas = options.canvas || "c";
if (canvas.length !== 1 || "cwb".indexOf(canvas) === -1) {
callback("Invalid canvas " + canvas);
return;
}
var sharpen = !!options.sharpen;
var progessive = !!options.progessive;
var sequentialRead = !!options.sequentialRead;
sharp.resize(options.inFile, options.inBuffer, output, width, height, canvas, sharpen, progessive, sequentialRead, callback);
};
module.exports.cache = function(limit) {
if (Number.isNaN(limit)) {
limit = null;
}
return sharp.cache(limit);
}
/* Deprecated v0.0.x methods */
module.exports.crop = function(input, output, width, height, sharpen, callback) {
sharp.resize(input, output, width, height, {canvas: "c", sharpen: true}, callback);
};
module.exports.embedWhite = function(input, output, width, height, callback) { module.exports.embedWhite = function(input, output, width, height, callback) {
sharp.resize(input, output, width, height, "w", callback); sharp.resize(input, output, width, height, {canvas: "w", sharpen: true}, callback);
}; };
module.exports.embedBlack = function(input, output, width, height, callback) { module.exports.embedBlack = function(input, output, width, height, callback) {
sharp.resize(input, output, width, height, "b", callback); sharp.resize(input, output, width, height, {canvas: "b", sharpen: true}, callback);
}; };

View File

@@ -1,8 +1,8 @@
{ {
"name": "sharp", "name": "sharp",
"version": "0.0.8", "version": "0.1.7",
"author": "Lovell Fuller", "author": "Lovell Fuller",
"description": "High performance module to resize JPEG and PNG images using the libvips image processing library", "description": "High performance module to resize JPEG, PNG, WebP and TIFF images using the libvips image processing library",
"scripts": { "scripts": {
"test": "node tests/perf.js" "test": "node tests/perf.js"
}, },
@@ -14,12 +14,15 @@
"keywords": [ "keywords": [
"jpeg", "jpeg",
"png", "png",
"webp",
"tiff",
"resize", "resize",
"thumbnail", "thumbnail",
"sharpen", "sharpen",
"crop", "crop",
"embed", "embed",
"libvips", "libvips",
"vips",
"fast", "fast",
"buffer" "buffer"
], ],

View File

@@ -1,183 +1,286 @@
#include <node.h> #include <node.h>
#include <node_buffer.h>
#include <math.h> #include <math.h>
#include <string> #include <string>
#include <vector> #include <string.h>
#include <vips/vips.h> #include <vips/vips.h>
#include <node_buffer.h>
using namespace v8; using namespace v8;
using namespace node; using namespace node;
// Free VipsImage children when object goes out of scope struct resize_baton {
// Thanks due to https://github.com/dosx/node-vips std::string file_in;
class ImageFreer { void* buffer_in;
public: size_t buffer_in_len;
ImageFreer() {} std::string file_out;
~ImageFreer() {
for (uint16_t i = 0; i < v_.size(); i++) {
if (v_[i] != NULL) {
g_object_unref(v_[i]);
}
}
v_.clear();
}
void add(VipsImage* i) { v_.push_back(i); }
private:
std::vector<VipsImage*> v_;
};
struct ResizeBaton {
std::string src;
std::string dst;
void* buffer_out; void* buffer_out;
size_t buffer_out_len; size_t buffer_out_len;
int cols; int width;
int rows; int height;
bool crop; bool crop;
int embed; VipsExtend extend;
bool sharpen;
bool progessive;
VipsAccess access_method;
std::string err; std::string err;
Persistent<Function> callback; Persistent<Function> callback;
ResizeBaton() : buffer_out_len(0) {} resize_baton(): buffer_in_len(0), buffer_out_len(0) {}
}; };
bool EndsWith(std::string const &str, std::string const &end) { typedef enum {
JPEG,
PNG,
WEBP,
TIFF
} ImageType;
unsigned char MARKER_JPEG[] = {0xff, 0xd8};
unsigned char MARKER_PNG[] = {0x89, 0x50};
unsigned char MARKER_WEBP[] = {0x52, 0x49};
bool ends_with(std::string const &str, std::string const &end) {
return str.length() >= end.length() && 0 == str.compare(str.length() - end.length(), end.length(), end); return str.length() >= end.length() && 0 == str.compare(str.length() - end.length(), end.length(), end);
} }
void ResizeAsync(uv_work_t *work) { bool is_jpeg(std::string const &str) {
ResizeBaton* baton = static_cast<ResizeBaton*>(work->data); return ends_with(str, ".jpg") || ends_with(str, ".jpeg") || ends_with(str, ".JPG") || ends_with(str, ".JPEG");
}
VipsImage *in = vips_image_new_mode((baton->src).c_str(), "p"); bool is_png(std::string const &str) {
if (EndsWith(baton->src, ".jpg") || EndsWith(baton->src, ".jpeg")) { return ends_with(str, ".png") || ends_with(str, ".PNG");
vips_jpegload((baton->src).c_str(), &in, NULL); }
} else if (EndsWith(baton->src, ".png")) {
vips_pngload((baton->src).c_str(), &in, NULL); bool is_webp(std::string const &str) {
return ends_with(str, ".webp") || ends_with(str, ".WEBP");
}
bool is_tiff(std::string const &str) {
return ends_with(str, ".tif") || ends_with(str, ".tiff") || ends_with(str, ".TIF") || ends_with(str, ".TIFF");
}
void resize_error(resize_baton *baton, VipsImage *unref) {
(baton->err).append(vips_error_buffer());
vips_error_clear();
g_object_unref(unref);
vips_thread_shutdown();
return;
}
void resize_async(uv_work_t *work) {
resize_baton* baton = static_cast<resize_baton*>(work->data);
ImageType inputImageType = JPEG;
VipsImage *in = vips_image_new();
if (baton->buffer_in_len > 1) {
if (memcmp(MARKER_JPEG, baton->buffer_in, 2) == 0) {
if (vips_jpegload_buffer(baton->buffer_in, baton->buffer_in_len, &in, "access", baton->access_method, NULL)) {
return resize_error(baton, in);
}
} else if(memcmp(MARKER_PNG, baton->buffer_in, 2) == 0) {
inputImageType = PNG;
if (vips_pngload_buffer(baton->buffer_in, baton->buffer_in_len, &in, "access", baton->access_method, NULL)) {
return resize_error(baton, in);
}
} else if(memcmp(MARKER_WEBP, baton->buffer_in, 2) == 0) {
inputImageType = WEBP;
if (vips_webpload_buffer(baton->buffer_in, baton->buffer_in_len, &in, "access", baton->access_method, NULL)) {
return resize_error(baton, in);
}
} else {
resize_error(baton, in);
(baton->err).append("Unsupported input buffer");
return;
}
} else if (is_jpeg(baton->file_in)) {
if (vips_jpegload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
return resize_error(baton, in);
}
} else if (is_png(baton->file_in)) {
inputImageType = PNG;
if (vips_pngload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
return resize_error(baton, in);
}
} else if (is_webp(baton->file_in)) {
inputImageType = WEBP;
if (vips_webpload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
return resize_error(baton, in);
}
} else if (is_tiff(baton->file_in)) {
inputImageType = TIFF;
if (vips_tiffload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
return resize_error(baton, in);
}
} else { } else {
(baton->err).append("Unsupported input file type"); resize_error(baton, in);
return; (baton->err).append("Unsupported input file " + baton->file_in);
}
if (in == NULL) {
(baton->err).append(vips_error_buffer());
vips_error_clear();
return;
}
ImageFreer freer;
freer.add(in);
VipsImage* img = in;
VipsImage* t[4];
if (im_open_local_array(img, t, 4, "temp", "p")) {
(baton->err).append(vips_error_buffer());
vips_error_clear();
return; return;
} }
double xfactor = static_cast<double>(img->Xsize) / std::max(baton->cols, 1); double xfactor = static_cast<double>(in->Xsize) / std::max(baton->width, 1);
double yfactor = static_cast<double>(img->Ysize) / std::max(baton->rows, 1); double yfactor = static_cast<double>(in->Ysize) / std::max(baton->height, 1);
double factor = baton->crop ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor); double factor = baton->crop ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor);
factor = std::max(factor, 1.0); factor = std::max(factor, 1.0);
int shrink = floor(factor); int shrink = floor(factor);
double residual = shrink / factor; double residual = shrink / factor;
// Use im_shrink with the integral reduction // Try to use libjpeg shrink-on-load
if (im_shrink(img, t[0], shrink, shrink)) { int shrink_on_load = 1;
(baton->err).append(vips_error_buffer()); if (inputImageType == JPEG) {
vips_error_clear(); if (shrink >= 8) {
return; factor = factor / 8;
} shrink_on_load = 8;
} else if (shrink >= 4) {
// Use im_affinei with the remaining float part using bilinear interpolation factor = factor / 4;
if (im_affinei_all(t[0], t[1], vips_interpolate_bilinear_static(), residual, 0, 0, residual, 0, 0)) { shrink_on_load = 4;
(baton->err).append(vips_error_buffer()); } else if (shrink >= 2) {
vips_error_clear(); factor = factor / 2;
return; shrink_on_load = 2;
} }
img = t[1]; }
VipsImage *shrunk_on_load = vips_image_new();
if (baton->crop) { if (shrink_on_load > 1) {
int width = std::min(img->Xsize, baton->cols); // Recalculate integral shrink and double residual
int height = std::min(img->Ysize, baton->rows); factor = std::max(factor, 1.0);
int left = (img->Xsize - width + 1) / 2; shrink = floor(factor);
int top = (img->Ysize - height + 1) / 2; residual = shrink / factor;
if (im_extract_area(img, t[2], left, top, width, height)) { // Reload input using shrink-on-load
(baton->err).append(vips_error_buffer()); if (baton->buffer_in_len > 1) {
vips_error_clear(); if (vips_jpegload_buffer(baton->buffer_in, baton->buffer_in_len, &shrunk_on_load, "shrink", shrink_on_load, NULL)) {
return; return resize_error(baton, in);
}
} else {
if (vips_jpegload((baton->file_in).c_str(), &shrunk_on_load, "shrink", shrink_on_load, NULL)) {
return resize_error(baton, in);
}
} }
img = t[2];
} else { } else {
int left = (baton->cols - img->Xsize) / 2; vips_copy(in, &shrunk_on_load, NULL);
int top = (baton->rows - img->Ysize) / 2;
if (im_embed(img, t[2], baton->embed, left, top, baton->cols, baton->rows)) {
(baton->err).append(vips_error_buffer());
vips_error_clear();
return;
}
img = t[2];
} }
g_object_unref(in);
VipsImage *shrunk = vips_image_new();
if (shrink > 1) {
// Use vips_shrink with the integral reduction
if (vips_shrink(shrunk_on_load, &shrunk, shrink, shrink, NULL)) {
return resize_error(baton, shrunk_on_load);
}
} else {
vips_copy(shrunk_on_load, &shrunk, NULL);
}
g_object_unref(shrunk_on_load);
// Use vips_affine with the remaining float part using bilinear interpolation
VipsImage *affined = vips_image_new();
if (residual > 0) {
if (vips_affine(shrunk, &affined, residual, 0, 0, residual, "interpolate", vips_interpolate_bilinear_static(), NULL)) {
return resize_error(baton, shrunk);
}
} else {
vips_copy(shrunk, &affined, NULL);
}
g_object_unref(shrunk);
VipsImage *canvased = vips_image_new();
if (affined->Xsize != baton->width || affined->Ysize != baton->height) {
if (baton->crop) {
// Crop
int width = std::min(affined->Xsize, baton->width);
int height = std::min(affined->Ysize, baton->height);
int left = (affined->Xsize - width + 1) / 2;
int top = (affined->Ysize - height + 1) / 2;
if (vips_extract_area(affined, &canvased, left, top, width, height, NULL)) {
return resize_error(baton, affined);
}
} else {
// Embed
int left = (baton->width - affined->Xsize) / 2;
int top = (baton->height - affined->Ysize) / 2;
if (vips_embed(affined, &canvased, left, top, baton->width, baton->height, "extend", baton->extend, NULL)) {
return resize_error(baton, affined);
}
}
} else {
vips_copy(affined, &canvased, NULL);
}
g_object_unref(affined);
// Mild sharpen // Mild sharpen
INTMASK* sharpen = im_create_imaskv("sharpen", 3, 3, VipsImage *sharpened = vips_image_new();
-1, -1, -1, if (baton->sharpen) {
-1, 32, -1, INTMASK* sharpen = im_create_imaskv("sharpen", 3, 3,
-1, -1, -1); -1, -1, -1,
sharpen->scale = 24; -1, 32, -1,
if (im_conv(img, t[3], sharpen)) { -1, -1, -1);
(baton->err).append(vips_error_buffer()); sharpen->scale = 24;
vips_error_clear(); if (im_conv(canvased, sharpened, sharpen)) {
return; return resize_error(baton, canvased);
}
img = t[3];
if (baton->dst == "__jpeg") {
// Write JPEG to buffer
if (vips_jpegsave_buffer(img, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", 80, "optimize_coding", TRUE, NULL)) {
(baton->err).append(vips_error_buffer());
vips_error_clear();
return;
}
} else if (baton->dst == "__png") {
// Write PNG to buffer
if (vips_pngsave_buffer(img, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "compression", 6, "interlace", FALSE, NULL)) {
(baton->err).append(vips_error_buffer());
vips_error_clear();
return;
}
} else if (EndsWith(baton->dst, ".jpg") || EndsWith(baton->dst, ".jpeg")) {
// Write JPEG to file
if (vips_foreign_save(img, baton->dst.c_str(), "strip", TRUE, "Q", 80, "optimize_coding", TRUE, NULL)) {
(baton->err).append(vips_error_buffer());
vips_error_clear();
}
} else if (EndsWith(baton->dst, ".png")) {
// Write PNG to file
if (vips_foreign_save(img, baton->dst.c_str(), "strip", TRUE, "compression", 6, "interlace", FALSE, NULL)) {
(baton->err).append(vips_error_buffer());
vips_error_clear();
} }
} else { } else {
(baton->err).append("Unsupported output file type"); vips_copy(canvased, &sharpened, NULL);
} }
g_object_unref(canvased);
if (baton->file_out == "__jpeg") {
// Write JPEG to buffer
if (vips_jpegsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", 80, "optimize_coding", TRUE, "interlace", baton->progessive, NULL)) {
return resize_error(baton, sharpened);
}
} else if (baton->file_out == "__png") {
// Write PNG to buffer
if (vips_pngsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "compression", 6, "interlace", baton->progessive, NULL)) {
return resize_error(baton, sharpened);
}
} else if (baton->file_out == "__webp") {
// Write WEBP to buffer
if (vips_webpsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", 80, NULL)) {
return resize_error(baton, sharpened);
}
} else if (is_jpeg(baton->file_out)) {
// Write JPEG to file
if (vips_jpegsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", 80, "optimize_coding", TRUE, "interlace", baton->progessive, NULL)) {
return resize_error(baton, sharpened);
}
} else if (is_png(baton->file_out)) {
// Write PNG to file
if (vips_pngsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", 6, "interlace", baton->progessive, NULL)) {
return resize_error(baton, sharpened);
}
} else if (is_webp(baton->file_out)) {
// Write WEBP to file
if (vips_webpsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", 80, NULL)) {
return resize_error(baton, sharpened);
}
} else if (is_tiff(baton->file_out)) {
// Write TIFF to file
if (vips_tiffsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", 80, NULL)) {
return resize_error(baton, sharpened);
}
} else {
(baton->err).append("Unsupported output " + baton->file_out);
}
g_object_unref(sharpened);
vips_thread_shutdown(); vips_thread_shutdown();
} }
void ResizeAsyncAfter(uv_work_t *work, int status) { void resize_async_after(uv_work_t *work, int status) {
HandleScope scope; HandleScope scope;
ResizeBaton *baton = static_cast<ResizeBaton*>(work->data); resize_baton *baton = static_cast<resize_baton*>(work->data);
Local<Value> null = Local<Value>::New(Null()); Handle<Value> argv[2] = { Null(), Null() };
Local<Value> argv[2] = {null, null};
if (!baton->err.empty()) { if (!baton->err.empty()) {
// Error // Error
argv[0] = String::New(baton->err.data(), baton->err.size()); argv[0] = scope.Close(String::New(baton->err.data(), baton->err.size()));
} else if (baton->buffer_out_len > 0) { } else if (baton->buffer_out_len > 0) {
// Buffer // Buffer
Buffer *buffer = Buffer::New((const char*)(baton->buffer_out), baton->buffer_out_len); Buffer *slowBuffer = Buffer::New(baton->buffer_out_len);
argv[1] = Local<Object>::New(buffer->handle_); memcpy(Buffer::Data(slowBuffer), baton->buffer_out, baton->buffer_out_len);
vips_free(baton->buffer_out); Local<Object> globalObj = Context::GetCurrent()->Global();
Local<Function> bufferConstructor = Local<Function>::Cast(globalObj->Get(String::New("Buffer")));
Handle<Value> constructorArgs[3] = { slowBuffer->handle_, v8::Integer::New(baton->buffer_out_len), v8::Integer::New(0) };
argv[1] = scope.Close(bufferConstructor->NewInstance(3, constructorArgs));
g_free(baton->buffer_out);
} }
baton->callback->Call(Context::GetCurrent()->Global(), 2, argv); baton->callback->Call(Context::GetCurrent()->Global(), 2, argv);
@@ -186,30 +289,54 @@ void ResizeAsyncAfter(uv_work_t *work, int status) {
delete work; delete work;
} }
Handle<Value> Resize(const Arguments& args) { Handle<Value> resize(const Arguments& args) {
HandleScope scope; HandleScope scope;
ResizeBaton *baton = new ResizeBaton; resize_baton *baton = new resize_baton;
baton->src = *String::Utf8Value(args[0]->ToString()); baton->file_in = *String::Utf8Value(args[0]->ToString());
baton->dst = *String::Utf8Value(args[1]->ToString()); if (args[1]->IsObject()) {
baton->cols = args[2]->Int32Value(); Local<Object> buffer = args[1]->ToObject();
baton->rows = args[3]->Int32Value(); baton->buffer_in_len = Buffer::Length(buffer);
Local<String> canvas = args[4]->ToString(); baton->buffer_in = Buffer::Data(buffer);
}
baton->file_out = *String::Utf8Value(args[2]->ToString());
baton->width = args[3]->Int32Value();
baton->height = args[4]->Int32Value();
Local<String> canvas = args[5]->ToString();
if (canvas->Equals(String::NewSymbol("c"))) { if (canvas->Equals(String::NewSymbol("c"))) {
baton->crop = true; baton->crop = true;
} else if (canvas->Equals(String::NewSymbol("w"))) { } else if (canvas->Equals(String::NewSymbol("w"))) {
baton->crop = false; baton->crop = false;
baton->embed = 4; baton->extend = VIPS_EXTEND_WHITE;
} else if (canvas->Equals(String::NewSymbol("b"))) { } else if (canvas->Equals(String::NewSymbol("b"))) {
baton->crop = false; baton->crop = false;
baton->embed = 0; baton->extend = VIPS_EXTEND_BLACK;
} }
baton->callback = Persistent<Function>::New(Local<Function>::Cast(args[5])); baton->sharpen = args[6]->BooleanValue();
baton->progessive = args[7]->BooleanValue();
baton->access_method = args[8]->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
baton->callback = Persistent<Function>::New(Local<Function>::Cast(args[9]));
uv_work_t *work = new uv_work_t; uv_work_t *work = new uv_work_t;
work->data = baton; work->data = baton;
uv_queue_work(uv_default_loop(), work, ResizeAsync, (uv_after_work_cb)ResizeAsyncAfter); uv_queue_work(uv_default_loop(), work, resize_async, (uv_after_work_cb)resize_async_after);
return Undefined(); return scope.Close(Undefined());
}
Handle<Value> cache(const Arguments& args) {
HandleScope scope;
// Set cache limit
if (args[0]->IsInt32()) {
vips_cache_set_max_mem(args[0]->Int32Value() * 1048576);
}
// Get cache statistics
Local<Object> cache = Object::New();
cache->Set(String::NewSymbol("current"), Number::New(vips_tracked_get_mem() / 1048576));
cache->Set(String::NewSymbol("high"), Number::New(vips_tracked_get_mem_highwater() / 1048576));
cache->Set(String::NewSymbol("limit"), Number::New(vips_cache_get_max_mem() / 1048576));
return scope.Close(cache);
} }
static void at_exit(void* arg) { static void at_exit(void* arg) {
@@ -221,7 +348,8 @@ extern "C" void init(Handle<Object> target) {
HandleScope scope; HandleScope scope;
vips_init(""); vips_init("");
AtExit(at_exit); AtExit(at_exit);
NODE_SET_METHOD(target, "resize", Resize); NODE_SET_METHOD(target, "resize", resize);
}; NODE_SET_METHOD(target, "cache", cache);
}
NODE_MODULE(sharp, init); NODE_MODULE(sharp, init)

BIN
tests/4.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

BIN
tests/G31D.TIF Normal file

Binary file not shown.

31
tests/parallel.js Executable file
View File

@@ -0,0 +1,31 @@
var sharp = require("../index");
var fs = require("fs");
var assert = require("assert");
var async = require("async");
var inputJpg = __dirname + "/2569067123_aca715a2ee_o.jpg"; // http://www.flickr.com/photos/grizdave/2569067123/
var width = 720;
var height = 480;
async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64, 128], function(parallelism, next) {
var start = new Date().getTime();
async.times(parallelism,
function(id, callback) {
sharp.resize(inputJpg, sharp.buffer.jpeg, width, height, function(err, buffer) {
buffer = null;
callback(err, new Date().getTime() - start);
});
},
function(err, ids) {
assert(!err);
assert(ids.length === parallelism);
var mean = ids.reduce(function(a, b) {
return a + b;
}) / ids.length;
console.log(parallelism + " parallel calls: fastest=" + ids[0] + "ms slowest=" + ids[ids.length - 1] + "ms mean=" + mean + "ms");
next();
}
);
}, function() {
console.dir(sharp.cache());
});

View File

@@ -1,4 +1,5 @@
var sharp = require("../index"); var sharp = require("../index");
var fs = require("fs");
var imagemagick = require("imagemagick"); var imagemagick = require("imagemagick");
var gm = require("gm"); var gm = require("gm");
var epeg = require("epeg"); var epeg = require("epeg");
@@ -8,17 +9,22 @@ var Benchmark = require("benchmark");
var inputJpg = __dirname + "/2569067123_aca715a2ee_o.jpg"; // http://www.flickr.com/photos/grizdave/2569067123/ var inputJpg = __dirname + "/2569067123_aca715a2ee_o.jpg"; // http://www.flickr.com/photos/grizdave/2569067123/
var outputJpg = __dirname + "/output.jpg"; var outputJpg = __dirname + "/output.jpg";
var outputJpgLength = 47035;
var inputPng = __dirname + "/50020484-00001.png"; // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png var inputPng = __dirname + "/50020484-00001.png"; // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
var outputPng = __dirname + "/output.png"; var outputPng = __dirname + "/output.png";
var outputPngLength = 60380;
var width = 640; var inputWebp = __dirname + "/4.webp"; // http://www.gstatic.com/webp/gallery/4.webp
var outputWebp = __dirname + "/output.webp";
var inputTiff = __dirname + "/G31D.TIF"; // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
var outputTiff = __dirname + "/output.tiff";
var width = 720;
var height = 480; var height = 480;
async.series({ async.series({
jpeg: function(callback) { jpeg: function(callback) {
var inputJpgBuffer = fs.readFileSync(inputJpg);
(new Benchmark.Suite("jpeg")).add("imagemagick", { (new Benchmark.Suite("jpeg")).add("imagemagick", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
@@ -36,10 +42,10 @@ async.series({
} }
}); });
} }
}).add("gm", { }).add("gm-file-file", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
gm(inputJpg).crop(width, height).quality(80).write(outputJpg, function (err) { gm(inputJpg).resize(width, height).quality(80).write(outputJpg, function (err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -47,17 +53,37 @@ async.series({
} }
}); });
} }
}).add("epeg", { }).add("gm-file-buffer", {
defer: true,
fn: function(deferred) {
gm(inputJpg).resize(width, height).quality(80).toBuffer(function (err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add("epeg-file-file", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
var image = new epeg.Image({path: inputJpg}); var image = new epeg.Image({path: inputJpg});
image.downsize(width, height, 80).saveTo(outputJpg); image.downsize(width, height, 80).saveTo(outputJpg);
deferred.resolve(); deferred.resolve();
} }
}).add("sharp-file", { }).add("epeg-file-buffer", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.crop(inputJpg, outputJpg, width, height, function(err) { var image = new epeg.Image({path: inputJpg});
var buffer = image.downsize(width, height, 80).process();
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
}).add("sharp-buffer-file", {
defer: true,
fn: function(deferred) {
sharp.resize(inputJpgBuffer, outputJpg, width, height, function(err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -65,15 +91,73 @@ async.series({
} }
}); });
} }
}).add("sharp-buffer", { }).add("sharp-buffer-buffer", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.crop(inputJpg, sharp.buffer.jpeg, width, height, function(err, buffer) { sharp.resize(inputJpgBuffer, sharp.buffer.jpeg, width, height, function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add("sharp-file-file", {
defer: true,
fn: function(deferred) {
sharp.resize(inputJpg, outputJpg, width, height, function(err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add("sharp-file-buffer", {
defer: true,
fn: function(deferred) {
sharp.resize(inputJpg, sharp.buffer.jpeg, width, height, function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add("sharp-file-buffer-sharpen", {
defer: true,
fn: function(deferred) {
sharp.resize(inputJpg, sharp.buffer.jpeg, width, height, {sharpen: true}, function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add("sharp-file-buffer-progressive", {
defer: true,
fn: function(deferred) {
sharp.resize(inputJpg, sharp.buffer.jpeg, width, height, {progressive: true}, function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add("sharp-file-buffer-sequentialRead", {
defer: true,
fn: function(deferred) {
sharp.resize(inputJpg, sharp.buffer.jpeg, width, height, {sequentialRead: true}, function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
assert.notStrictEqual(null, buffer); assert.notStrictEqual(null, buffer);
assert.strictEqual(outputJpgLength, buffer.length);
deferred.resolve(); deferred.resolve();
} }
}); });
@@ -85,6 +169,7 @@ async.series({
}).run(); }).run();
}, },
png: function(callback) { png: function(callback) {
var inputPngBuffer = fs.readFileSync(inputPng);
(new Benchmark.Suite("png")).add("imagemagick", { (new Benchmark.Suite("png")).add("imagemagick", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
@@ -101,10 +186,10 @@ async.series({
} }
}); });
} }
}).add("gm", { }).add("gm-file-file", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
gm(inputPng).crop(width, height).write(outputPng, function (err) { gm(inputPng).resize(width, height).write(outputPng, function (err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -112,26 +197,96 @@ async.series({
} }
}); });
} }
}).add("sharp-file", { }).add("gm-file-buffer", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.crop(inputPng, outputPng, width, height, function(err) { gm(inputPng).resize(width, height).quality(80).toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
deferred.resolve(); assert.notStrictEqual(null, buffer);
} deferred.resolve();
}); }
} });
}).add("sharp-buffer", { }
defer: true, }).add("sharp-buffer-file", {
fn: function(deferred) { defer: true,
sharp.crop(inputPng, sharp.buffer.png, width, height, function(err, buffer) { fn: function(deferred) {
sharp.resize(inputPngBuffer, outputPng, width, height, function(err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add("sharp-buffer-buffer", {
defer: true,
fn: function(deferred) {
sharp.resize(inputPngBuffer, sharp.buffer.png, width, height, function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add("sharp-file-file", {
defer: true,
fn: function(deferred) {
sharp.resize(inputPng, outputPng, width, height, function(err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add("sharp-file-buffer", {
defer: true,
fn: function(deferred) {
sharp.resize(inputPng, sharp.buffer.png, width, height, function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add("sharp-file-buffer-sharpen", {
defer: true,
fn: function(deferred) {
sharp.resize(inputPng, sharp.buffer.png, width, height, {sharpen: true}, function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add("sharp-file-buffer-progressive", {
defer: true,
fn: function(deferred) {
sharp.resize(inputPng, sharp.buffer.png, width, height, {progressive: true}, function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add("sharp-file-buffer-sequentialRead", {
defer: true,
fn: function(deferred) {
sharp.resize(inputPng, sharp.buffer.png, width, height, {sequentialRead: true}, function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
assert.notStrictEqual(null, buffer); assert.notStrictEqual(null, buffer);
assert.strictEqual(outputPngLength, buffer.length);
deferred.resolve(); deferred.resolve();
} }
}); });
@@ -141,10 +296,129 @@ async.series({
}).on("complete", function() { }).on("complete", function() {
callback(null, this.filter("fastest").pluck("name")); callback(null, this.filter("fastest").pluck("name"));
}).run(); }).run();
} },
webp: function(callback) {
var inputWebpBuffer = fs.readFileSync(inputWebp);
(new Benchmark.Suite("webp")).add("sharp-buffer-file", {
defer: true,
fn: function(deferred) {
sharp.resize(inputWebpBuffer, outputWebp, width, height, function(err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add("sharp-buffer-buffer", {
defer: true,
fn: function(deferred) {
sharp.resize(inputWebpBuffer, sharp.buffer.webp, width, height, function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add("sharp-file-file", {
defer: true,
fn: function(deferred) {
sharp.resize(inputWebp, outputWebp, width, height, function(err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add("sharp-file-buffer", {
defer: true,
fn: function(deferred) {
sharp.resize(inputWebp, sharp.buffer.webp, width, height, function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add("sharp-file-buffer-sharpen", {
defer: true,
fn: function(deferred) {
sharp.resize(inputWebp, sharp.buffer.webp, width, height, {sharpen: true}, function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add("sharp-file-buffer-sequentialRead", {
defer: true,
fn: function(deferred) {
sharp.resize(inputWebp, sharp.buffer.webp, width, height, {sequentialRead: true}, function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).on("cycle", function(event) {
console.log("webp " + String(event.target));
}).on("complete", function() {
callback(null, this.filter("fastest").pluck("name"));
}).run();
},
tiff: function(callback) {
(new Benchmark.Suite("tiff")).add("sharp-file-file", {
defer: true,
fn: function(deferred) {
sharp.resize(inputTiff, outputTiff, width, height, function(err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add("sharp-file-file-sharpen", {
defer: true,
fn: function(deferred) {
sharp.resize(inputTiff, outputTiff, width, height, {sharpen: true}, function(err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add("sharp-file-file-sequentialRead", {
defer: true,
fn: function(deferred) {
sharp.resize(inputTiff, outputTiff, width, height, {sequentialRead: true}, function(err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).on("cycle", function(event) {
console.log("tiff " + String(event.target));
}).on("complete", function() {
callback(null, this.filter("fastest").pluck("name"));
}).run();
}
}, function(err, results) { }, function(err, results) {
assert(!err, err); assert(!err, err);
Object.keys(results).forEach(function(format) { Object.keys(results).forEach(function(format) {
assert.strictEqual("sharp", results[format].toString().substr(0, 5), "sharp was slower than " + results[format] + " for " + format); assert.strictEqual("sharp", results[format].toString().substr(0, 5), "sharp was slower than " + results[format] + " for " + format);
}); });
console.dir(sharp.cache());
}); });