Breaking change to API to become more expressive (see #8). Add support for upscaling.

This commit is contained in:
Lovell Fuller 2014-03-09 21:44:03 +00:00
parent d0e6a4c0f3
commit 5f61331d1a
8 changed files with 272 additions and 146 deletions

140
README.md
View File

@ -2,10 +2,10 @@
_adj_ _adj_
1. clearly defined; distinct: a sharp photographic image. 1. clearly defined; distinct: a sharp photographic image.
2. quick, brisk, or spirited. 2. quick, brisk, or spirited.
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, PNG, WebP and TIFF images to smaller 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.
@ -13,19 +13,20 @@ The performance of JPEG resizing is typically 15x-25x faster than ImageMagick an
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_. 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_.
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. Anyone who has used the Node.js bindings for [GraphicsMagick](https://github.com/aheckmann/gm) will find the API similarly expressive.
This module is powered by 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+
* [libvips](https://github.com/jcupitt/libvips) v7.38.5+ * [libvips](https://github.com/jcupitt/libvips) v7.38.5+
### Install libvips on Mac OS via homebrew ### Install libvips on Mac OS
brew tap homebrew/science brew install homebrew/science/vips
brew install vips
### Install libvips on Ubuntu Linux ### Install libvips on Ubuntu/Debian Linux
sudo apt-get install automake build-essential git gobject-introspection gtk-doc-tools libfftw3-dev libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff5-dev liborc-0.4-dev libxml2-dev swig sudo apt-get install automake build-essential git gobject-introspection gtk-doc-tools libfftw3-dev libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff5-dev liborc-0.4-dev libxml2-dev swig
git clone https://github.com/jcupitt/libvips.git git clone https://github.com/jcupitt/libvips.git
@ -40,35 +41,14 @@ Under the hood you'll find the blazingly fast [libvips](https://github.com/jcupi
npm install sharp npm install sharp
## Usage ## Usage examples
var sharp = require("sharp");
### resize(input, output, width, height, [options], callback)
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. Use a value of -1 to auto-scale the width to match the height.
`height` is the Number of pixels high the resultant image should be. Use a value of -1 to auto-scale the height to match the width.
`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.resize("input.jpg", "output.jpg", 300, 200, function(err) { var sharp = require('sharp');
```
```javascript
sharp('input.jpg').resize(300, 200).write('output.jpg', function(err) {
if (err) { if (err) {
throw err; throw err;
} }
@ -78,7 +58,7 @@ sharp.resize("input.jpg", "output.jpg", 300, 200, function(err) {
``` ```
```javascript ```javascript
sharp.resize("input.jpg", sharp.buffer.jpeg, -1, 200, {progressive: true}, function(err, buffer) { sharp('input.jpg').resize(null, 200).progressive().toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} }
@ -87,35 +67,105 @@ sharp.resize("input.jpg", sharp.buffer.jpeg, -1, 200, {progressive: true}, funct
``` ```
```javascript ```javascript
sharp.resize("input.webp", sharp.buffer.png, 300, -1, {sharpen: true}, function(err, buffer) { sharp('input.png').resize(300).sharpen().webp(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} }
// buffer contains sharpened PNG image data (converted from JPEG), 300 pixels wide // buffer contains sharpened WebP image data (converted from PNG), 300 pixels wide
}); });
``` ```
```javascript ```javascript
sharp.resize(buffer, "output.tiff", 200, 300, {canvas: sharp.canvas.embedWhite}, function(err) { sharp(buffer).resize(200, 300).embedWhite().write('output.tiff', function(err) {
if (err) { if (err) {
throw err; throw err;
} }
// output.tiff is a 200 pixels wide and 300 pixels high image containing a scaled version // output.tiff is a 200 pixels wide and 300 pixels high image containing a scaled
// of the image data contained in buffer embedded on a white canvas // version, embedded on a white canvas, of the image data in buffer
}); });
``` ```
```javascript ```javascript
sharp.resize("input.jpg", sharp.buffer.webp, 200, 300, {canvas: sharp.canvas.embedBlack}, function(err, buffer) { sharp('input.jpg').resize(200, 300).embedBlack().webp(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} }
// buffer contains WebP image data of 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, embedded on a black canvas, of input.png
}); });
``` ```
### cache([limit]) ## API
### sharp(input)
Constructor to which further methods are chained.
`input` can either be a filename String or a Buffer.
### resize(width, [height])
Scale to `width` x `height`. By default, the resized image is cropped to the exact size specified.
`width` is the Number of pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
`height` is the Number of pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
### crop()
Crop the resized image to the exact size specified, the default behaviour.
### embedWhite()
Embed the resized image on a white background of the exact size specified.
### embedBlack()
Embed the resized image on a black background of the exact size specified.
### sharpen()
Perform a mild sharpen of the resultant image. This typically reduces performance by 30%.
### progressive()
Use progressive (interlace) scan for the output. This typically reduces performance by 30%.
### sequentialRead()
An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. This will reduce memory usage and can improve performance on some systems.
### write(filename, callback)
`filename` is a String containing the filename to write the image data to. The format is inferred from the extension, with JPEG, PNG, WebP and TIFF supported.
`callback` is called with a single argument `(err)` containing an error message, if any.
### jpeg(callback)
Write JPEG image data to a Buffer.
`callback` gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant JPEG image data.
### png(callback)
Write PNG image data to a Buffer.
`callback` gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant PNG image data.
### webp(callback)
Write WebP image data to a Buffer.
`callback` gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant WebP image data.
### toBuffer(callback)
Write image data to a Buffer, the format of which will match the input image.
`callback` gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant image data.
### sharp.cache([limit])
If `limit` is provided, set the (soft) limit of _libvips_ working/cache memory to this value in MB. The default value is 100. If `limit` is provided, set the (soft) limit of _libvips_ working/cache memory to this value in MB. The default value is 100.

167
index.js
View File

@ -1,73 +1,128 @@
var sharp = require("./build/Release/sharp"); /*jslint node: true */
'use strict';
module.exports.buffer = { var sharp = require('./build/Release/sharp');
jpeg: "__jpeg",
png: "__png",
webp: "__webp"
};
module.exports.canvas = { var Sharp = function(input) {
crop: "c", if (!(this instanceof Sharp)) {
embedWhite: "w", return new Sharp(input);
embedBlack: "b"
};
module.exports.resize = function(input, output, width, height, options, callback) {
"use strict";
if (typeof options === 'function') {
callback = options;
options = {};
} else {
options = options || {};
} }
this.options = {
width: -1,
height: -1,
canvas: 'c',
sharpen: false,
progressive: false,
sequentialRead: false,
output: '__jpeg'
};
if (typeof input === 'string') { if (typeof input === 'string') {
options.inFile = input; this.options.inFile = input;
} else if (typeof input ==='object' && input instanceof Buffer) { } else if (typeof input ==='object' && input instanceof Buffer) {
options.inBuffer = input; this.options.inBuffer = input;
} else { } else {
callback("Unsupported input " + typeof input); throw 'Unsupported input ' + typeof input;
return;
} }
return this;
};
module.exports = Sharp;
Sharp.prototype.crop = function() {
this.options.canvas = 'c';
return this;
};
Sharp.prototype.embedWhite = function() {
this.options.canvas = 'w';
return this;
};
Sharp.prototype.embedBlack = function() {
this.options.canvas = 'b';
return this;
};
Sharp.prototype.sharpen = function(sharpen) {
this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true;
return this;
};
Sharp.prototype.progressive = function(progressive) {
this.options.progressive = (typeof progressive === 'boolean') ? progressive : true;
return this;
};
Sharp.prototype.sequentialRead = function(sequentialRead) {
this.options.sequentialRead = (typeof sequentialRead === 'boolean') ? sequentialRead : true;
return this;
};
Sharp.prototype.resize = function(width, height) {
if (!width) {
this.options.width = -1;
} else {
if (!Number.isNaN(width)) {
this.options.width = width;
} else {
throw 'Invalid width ' + width;
}
}
if (!height) {
this.options.height = -1;
} else {
if (!Number.isNaN(height)) {
this.options.height = height;
} else {
throw 'Invalid height ' + height;
}
}
return this;
};
Sharp.prototype.write = function(output, callback) {
if (!output || output.length === 0) { if (!output || output.length === 0) {
callback("Invalid output"); throw 'Invalid output';
return; } else {
this._sharp(output, callback);
} }
var outWidth = Number(width); return this;
if (Number.isNaN(outWidth)) { };
callback("Invalid width " + width);
return; Sharp.prototype.toBuffer = function(callback) {
} return this._sharp('__input', callback);
var outHeight = Number(height); };
if (Number.isNaN(outHeight)) {
callback("Invalid height " + height); Sharp.prototype.jpeg = function(callback) {
return; return this._sharp('__jpeg', callback);
} };
var canvas = options.canvas || "c";
if (canvas.length !== 1 || "cwb".indexOf(canvas) === -1) { Sharp.prototype.png = function(callback) {
callback("Invalid canvas " + canvas); return this._sharp('__png', callback);
return; };
}
var sharpen = !!options.sharpen; Sharp.prototype.webp = function(callback) {
var progessive = !!options.progessive; return this._sharp('__webp', callback);
var sequentialRead = !!options.sequentialRead; };
sharp.resize(options.inFile, options.inBuffer, output, outWidth, outHeight, canvas, sharpen, progessive, sequentialRead, callback);
Sharp.prototype._sharp = function(output, callback) {
sharp.resize(
this.options.inFile,
this.options.inBuffer,
output,
this.options.width,
this.options.height,
this.options.canvas,
this.options.sharpen,
this.options.progressive,
this.options.sequentialRead,
callback
);
return this;
}; };
module.exports.cache = function(limit) { module.exports.cache = function(limit) {
"use strict";
if (Number.isNaN(limit)) { if (Number.isNaN(limit)) {
limit = null; limit = null;
} }
return sharp.cache(limit); 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) {
sharp.resize(input, output, width, height, {canvas: "w", sharpen: true}, callback);
};
module.exports.embedBlack = function(input, output, width, height, callback) {
sharp.resize(input, output, width, height, {canvas: "b", sharpen: true}, callback);
};

View File

@ -1,6 +1,6 @@
{ {
"name": "sharp", "name": "sharp",
"version": "0.1.8", "version": "0.2.0",
"author": "Lovell Fuller", "author": "Lovell Fuller",
"description": "High performance module to resize JPEG, PNG, WebP and TIFF 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": {

View File

@ -70,6 +70,7 @@ void resize_error(resize_baton *baton, VipsImage *unref) {
void resize_async(uv_work_t *work) { void resize_async(uv_work_t *work) {
resize_baton* baton = static_cast<resize_baton*>(work->data); resize_baton* baton = static_cast<resize_baton*>(work->data);
// Input
ImageType inputImageType = JPEG; ImageType inputImageType = JPEG;
VipsImage *in = vips_image_new(); VipsImage *in = vips_image_new();
if (baton->buffer_in_len > 1) { if (baton->buffer_in_len > 1) {
@ -117,30 +118,32 @@ void resize_async(uv_work_t *work) {
return; return;
} }
double xfactor = static_cast<double>(in->Xsize) / std::max(baton->width, 1); // Scaling calculations
double yfactor = static_cast<double>(in->Ysize) / std::max(baton->height, 1);
double factor; double factor;
if (baton->width > 0 && baton->height > 0) { if (baton->width > 0 && baton->height > 0) {
// Fixed width and height // Fixed width and height
double xfactor = (double)(in->Xsize) / (double)(baton->width);
double yfactor = (double)(in->Ysize) / (double)(baton->height);
factor = baton->crop ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor); factor = baton->crop ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor);
} else if (baton->width > 0) { } else if (baton->width > 0) {
// Fixed width, auto height // Fixed width, auto height
factor = xfactor; factor = (double)(in->Xsize) / (double)(baton->width);
baton->height = floor(in->Ysize * factor); baton->height = floor((double)(in->Ysize) / factor);
} else if (baton->height > 0) { } else if (baton->height > 0) {
// Fixed height, auto width // Fixed height, auto width
factor = yfactor; factor = (double)(in->Ysize) / (double)(baton->height);
baton->width = floor(in->Xsize * factor); baton->width = floor((double)(in->Xsize) / factor);
} else { } else {
// Identity transform // Identity transform
factor = 1; factor = 1;
baton->width = in->Xsize; baton->width = in->Xsize;
baton->height = in->Ysize; baton->height = in->Ysize;
} }
factor = std::max(factor, 1.0);
int shrink = floor(factor); int shrink = floor(factor);
double residual = shrink / factor; if (shrink < 1) {
shrink = 1;
}
double residual = shrink / (double)factor;
// Try to use libjpeg shrink-on-load // Try to use libjpeg shrink-on-load
int shrink_on_load = 1; int shrink_on_load = 1;
@ -190,7 +193,7 @@ void resize_async(uv_work_t *work) {
// Use vips_affine with the remaining float part using bilinear interpolation // Use vips_affine with the remaining float part using bilinear interpolation
VipsImage *affined = vips_image_new(); VipsImage *affined = vips_image_new();
if (residual > 0) { if (residual != 0) {
if (vips_affine(shrunk, &affined, residual, 0, 0, residual, "interpolate", vips_interpolate_bilinear_static(), NULL)) { if (vips_affine(shrunk, &affined, residual, 0, 0, residual, "interpolate", vips_interpolate_bilinear_static(), NULL)) {
return resize_error(baton, shrunk); return resize_error(baton, shrunk);
} }
@ -199,6 +202,7 @@ void resize_async(uv_work_t *work) {
} }
g_object_unref(shrunk); g_object_unref(shrunk);
// Crop/embed
VipsImage *canvased = vips_image_new(); VipsImage *canvased = vips_image_new();
if (affined->Xsize != baton->width || affined->Ysize != baton->height) { if (affined->Xsize != baton->width || affined->Ysize != baton->height) {
if (baton->crop) { if (baton->crop) {
@ -239,17 +243,18 @@ void resize_async(uv_work_t *work) {
} }
g_object_unref(canvased); g_object_unref(canvased);
if (baton->file_out == "__jpeg") { // Output
if (baton->file_out == "__jpeg" || (baton->file_out == "__input" && inputImageType == JPEG)) {
// Write JPEG to buffer // 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)) { 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); return resize_error(baton, sharpened);
} }
} else if (baton->file_out == "__png") { } else if (baton->file_out == "__png" || (baton->file_out == "__input" && inputImageType == PNG)) {
// Write PNG to buffer // Write PNG to buffer
if (vips_pngsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "compression", 6, "interlace", baton->progessive, NULL)) { 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); return resize_error(baton, sharpened);
} }
} else if (baton->file_out == "__webp") { } else if (baton->file_out == "__webp" || (baton->file_out == "__input" && inputImageType == WEBP)) {
// Write WEBP to buffer // Write WEBP to buffer
if (vips_webpsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", 80, NULL)) { if (vips_webpsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", 80, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);

View File

@ -11,7 +11,7 @@ async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64, 128], function(parallelism, next) {
var start = new Date().getTime(); var start = new Date().getTime();
async.times(parallelism, async.times(parallelism,
function(id, callback) { function(id, callback) {
sharp.resize(inputJpg, sharp.buffer.jpeg, width, height, function(err, buffer) { sharp(inputJpg).resize(width, height).toBuffer(function(err, buffer) {
buffer = null; buffer = null;
callback(err, new Date().getTime() - start); callback(err, new Date().getTime() - start);
}); });

View File

@ -83,7 +83,7 @@ async.series({
}).add("sharp-buffer-file", { }).add("sharp-buffer-file", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputJpgBuffer, outputJpg, width, height, function(err) { sharp(inputJpgBuffer).resize(width, height).write(outputJpg, function(err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -94,7 +94,7 @@ async.series({
}).add("sharp-buffer-buffer", { }).add("sharp-buffer-buffer", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputJpgBuffer, sharp.buffer.jpeg, width, height, function(err, buffer) { sharp(inputJpgBuffer).resize(width, height).toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -106,7 +106,7 @@ async.series({
}).add("sharp-file-file", { }).add("sharp-file-file", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputJpg, outputJpg, width, height, function(err) { sharp(inputJpg).resize(width, height).write(outputJpg, function(err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -117,7 +117,7 @@ async.series({
}).add("sharp-file-buffer", { }).add("sharp-file-buffer", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputJpg, sharp.buffer.jpeg, width, height, function(err, buffer) { sharp(inputJpg).resize(width, height).toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -129,7 +129,7 @@ async.series({
}).add("sharp-file-buffer-sharpen", { }).add("sharp-file-buffer-sharpen", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputJpg, sharp.buffer.jpeg, width, height, {sharpen: true}, function(err, buffer) { sharp(inputJpg).resize(width, height).sharpen().toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -141,7 +141,7 @@ async.series({
}).add("sharp-file-buffer-progressive", { }).add("sharp-file-buffer-progressive", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputJpg, sharp.buffer.jpeg, width, height, {progressive: true}, function(err, buffer) { sharp(inputJpg).resize(width, height).progressive().toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -153,7 +153,7 @@ async.series({
}).add("sharp-file-buffer-sequentialRead", { }).add("sharp-file-buffer-sequentialRead", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputJpg, sharp.buffer.jpeg, width, height, {sequentialRead: true}, function(err, buffer) { sharp(inputJpg).resize(width, height).sequentialRead().toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -212,7 +212,7 @@ async.series({
}).add("sharp-buffer-file", { }).add("sharp-buffer-file", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputPngBuffer, outputPng, width, height, function(err) { sharp(inputPngBuffer).resize(width, height).write(outputPng, function(err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -223,7 +223,7 @@ async.series({
}).add("sharp-buffer-buffer", { }).add("sharp-buffer-buffer", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputPngBuffer, sharp.buffer.png, width, height, function(err, buffer) { sharp(inputPngBuffer).resize(width, height).toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -235,7 +235,7 @@ async.series({
}).add("sharp-file-file", { }).add("sharp-file-file", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputPng, outputPng, width, height, function(err) { sharp(inputPng).resize(width, height).write(outputPng, function(err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -246,7 +246,7 @@ async.series({
}).add("sharp-file-buffer", { }).add("sharp-file-buffer", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputPng, sharp.buffer.png, width, height, function(err, buffer) { sharp(inputPng).resize(width, height).toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -258,7 +258,7 @@ async.series({
}).add("sharp-file-buffer-sharpen", { }).add("sharp-file-buffer-sharpen", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputPng, sharp.buffer.png, width, height, {sharpen: true}, function(err, buffer) { sharp(inputPng).resize(width, height).sharpen().toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -270,7 +270,7 @@ async.series({
}).add("sharp-file-buffer-progressive", { }).add("sharp-file-buffer-progressive", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputPng, sharp.buffer.png, width, height, {progressive: true}, function(err, buffer) { sharp(inputPng).resize(width, height).progressive().toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -282,7 +282,7 @@ async.series({
}).add("sharp-file-buffer-sequentialRead", { }).add("sharp-file-buffer-sequentialRead", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputPng, sharp.buffer.png, width, height, {sequentialRead: true}, function(err, buffer) { sharp(inputPng).sequentialRead().resize(width, height).toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -302,7 +302,7 @@ async.series({
(new Benchmark.Suite("webp")).add("sharp-buffer-file", { (new Benchmark.Suite("webp")).add("sharp-buffer-file", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputWebpBuffer, outputWebp, width, height, function(err) { sharp(inputWebpBuffer).resize(width, height).write(outputWebp, function(err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -313,7 +313,7 @@ async.series({
}).add("sharp-buffer-buffer", { }).add("sharp-buffer-buffer", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputWebpBuffer, sharp.buffer.webp, width, height, function(err, buffer) { sharp(inputWebpBuffer).resize(width, height).toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -325,7 +325,7 @@ async.series({
}).add("sharp-file-file", { }).add("sharp-file-file", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputWebp, outputWebp, width, height, function(err) { sharp(inputWebp).resize(width, height).write(outputWebp, function(err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -336,7 +336,7 @@ async.series({
}).add("sharp-file-buffer", { }).add("sharp-file-buffer", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputWebp, sharp.buffer.webp, width, height, function(err, buffer) { sharp(inputWebp).resize(width, height).toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -348,7 +348,7 @@ async.series({
}).add("sharp-file-buffer-sharpen", { }).add("sharp-file-buffer-sharpen", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputWebp, sharp.buffer.webp, width, height, {sharpen: true}, function(err, buffer) { sharp(inputWebp).resize(width, height).sharpen().toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -360,7 +360,7 @@ async.series({
}).add("sharp-file-buffer-sequentialRead", { }).add("sharp-file-buffer-sequentialRead", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputWebp, sharp.buffer.webp, width, height, {sequentialRead: true}, function(err, buffer) { sharp(inputWebp).sequentialRead().resize(width, height).toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -379,7 +379,7 @@ async.series({
(new Benchmark.Suite("tiff")).add("sharp-file-file", { (new Benchmark.Suite("tiff")).add("sharp-file-file", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputTiff, outputTiff, width, height, function(err) { sharp(inputTiff).resize(width, height).write(outputTiff, function(err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -390,7 +390,7 @@ async.series({
}).add("sharp-file-file-sharpen", { }).add("sharp-file-file-sharpen", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputTiff, outputTiff, width, height, {sharpen: true}, function(err) { sharp(inputTiff).resize(width, height).sharpen().write(outputTiff, function(err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@ -401,7 +401,7 @@ async.series({
}).add("sharp-file-file-sequentialRead", { }).add("sharp-file-file-sequentialRead", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputTiff, outputTiff, width, height, {sequentialRead: true}, function(err) { sharp(inputTiff).sequentialRead().resize(width, height).write(outputTiff, function(err) {
if (err) { if (err) {
throw err; throw err;
} else { } else {

View File

@ -57,7 +57,7 @@ new Benchmark.Suite("random").add("imagemagick", {
}).add("sharp", { }).add("sharp", {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
sharp.resize(inputJpg, sharp.buffer.jpeg, randomDimension(), randomDimension(), function(err, buffer) { sharp(inputJpg).resize(randomDimension(), randomDimension()).toBuffer(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {

View File

@ -7,8 +7,9 @@ var inputJpg = __dirname + "/2569067123_aca715a2ee_o.jpg"; // http://www.flickr.
var outputJpg = __dirname + "/output.jpg"; var outputJpg = __dirname + "/output.jpg";
async.series([ async.series([
// Resize with exact crop
function(done) { function(done) {
sharp.resize(inputJpg, outputJpg, 320, 240, function(err) { sharp(inputJpg).resize(320, 240).write(outputJpg, function(err) {
if (err) throw err; if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) { imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err; if (err) throw err;
@ -18,30 +19,33 @@ async.series([
}); });
}); });
}, },
// Resize to fixed width
function(done) { function(done) {
sharp.resize(inputJpg, outputJpg, 320, -1, function(err) { sharp(inputJpg).resize(320).write(outputJpg, function(err) {
if (err) throw err; if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) { imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err; if (err) throw err;
assert.strictEqual(320, features.width); assert.strictEqual(320, features.width);
assert.strictEqual(262, features.height); assert.strictEqual(261, features.height);
done(); done();
}); });
}); });
}, },
// Resize to fixed height
function(done) { function(done) {
sharp.resize(inputJpg, outputJpg, -1, 320, function(err) { sharp(inputJpg).resize(null, 320).write(outputJpg, function(err) {
if (err) throw err; if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) { imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err; if (err) throw err;
assert.strictEqual(392, features.width); assert.strictEqual(391, features.width);
assert.strictEqual(320, features.height); assert.strictEqual(320, features.height);
done(); done();
}); });
}); });
}, },
// Identity transform
function(done) { function(done) {
sharp.resize(inputJpg, outputJpg, -1, -1, function(err) { sharp(inputJpg).write(outputJpg, function(err) {
if (err) throw err; if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) { imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err; if (err) throw err;
@ -50,5 +54,17 @@ async.series([
done(); done();
}); });
}); });
},
// Upscale
function(done) {
sharp(inputJpg).resize(3000).write(outputJpg, function(err) {
if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err;
assert.strictEqual(3000, features.width);
assert.strictEqual(2449, features.height);
done();
});
});
} }
]); ]);