Compare commits

..

7 Commits

Author SHA1 Message Date
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
6 changed files with 97 additions and 54 deletions

View File

@@ -9,9 +9,9 @@ _adj_
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.
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.
The performance of JPEG resizing is typically 15x-25x faster than ImageMagick and GraphicsMagick, based mainly on the number of CPU cores available.
Performance is up to 18x faster than ImageMagick and up to 8x 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
@@ -119,35 +119,35 @@ Test environment:
### JPEG
* imagemagick x 5.50 ops/sec ±0.48% (31 runs sampled)
* gm-file-file x 11.19 ops/sec ±0.51% (57 runs sampled)
* gm-file-buffer x 11.11 ops/sec ±0.42% (57 runs sampled)
* epeg-file-file x 28.59 ops/sec ±0.09% (71 runs sampled)
* epeg-file-buffer x 28.67 ops/sec ±0.14% (71 runs sampled)
* imagemagick x 5.53 ops/sec ±0.62% (31 runs sampled)
* gm-file-file x 4.10 ops/sec ±0.41% (25 runs sampled)
* gm-file-buffer x 4.10 ops/sec ±0.36% (25 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 24.72 ops/sec ±0.42% (62 runs sampled)
* sharp-buffer-buffer x 24.24 ops/sec ±0.36% (61 runs sampled)
* sharp-file-file x 97.15 ops/sec ±0.44% (80 runs sampled)
* sharp-file-buffer x __98.51 ops/sec__ ±0.42% (80 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 56.99 ops/sec ±5.43% (57 runs sampled)
* sharp-file-buffer-progressive x 64.89 ops/sec ±0.42% (79 runs sampled)
* sharp-file-buffer-sequentialRead x 64.13 ops/sec ±0.40% (78 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.31 ops/sec ±0.27% (26 runs sampled)
* gm-file-file x 17.89 ops/sec ±0.21% (86 runs sampled)
* gm-file-buffer x 14.74 ops/sec ±0.15% (73 runs sampled)
* 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.97 ops/sec ±120.47% (26 runs sampled)
* sharp-buffer-buffer x 13.00 ops/sec ±0.53% (65 runs sampled)
* sharp-file-file x 53.00 ops/sec ±7.15% (88 runs sampled)
* sharp-file-buffer x __55.43 ops/sec__ ±0.65% (89 runs sampled)
* sharp-file-buffer-sharpen x 45.37 ops/sec ±0.38% (74 runs sampled)
* sharp-file-buffer-progressive x 55.49 ops/sec ±0.45% (89 runs sampled)
* sharp-file-buffer-sequentialRead x 32.27 ops/sec ±0.29% (79 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)
## Licence

View File

@@ -7,6 +7,8 @@
'<!@(PKG_CONFIG_PATH="/usr/lib/pkgconfig" pkg-config --libs vips)'
],
'include_dirs': [
'/usr/local/include/glib-2.0',
'/usr/local/lib/glib-2.0/include',
'/usr/include/glib-2.0',
'/usr/lib/glib-2.0/include',
'/usr/lib/x86_64-linux-gnu/glib-2.0/include'

View File

@@ -1,6 +1,6 @@
{
"name": "sharp",
"version": "0.1.0",
"version": "0.1.3",
"author": "Lovell Fuller",
"description": "High performance module to resize JPEG and PNG images using the libvips image processing library",
"scripts": {

View File

@@ -18,7 +18,7 @@ struct resize_baton {
int width;
int height;
bool crop;
int embed;
VipsExtend extend;
bool sharpen;
bool progessive;
VipsAccess access_method;
@@ -98,19 +98,21 @@ void resize_async(uv_work_t *work) {
int shrink_on_load = 1;
if (inputImageType == JPEG) {
if (shrink >= 8) {
residual = residual * shrink / 8;
factor = factor / 8;
shrink_on_load = 8;
shrink = 1;
} else if (shrink >= 4) {
residual = residual * shrink / 4;
factor = factor / 4;
shrink_on_load = 4;
shrink = 1;
} else if (shrink >= 2) {
residual = residual * shrink / 2;
factor = factor / 2;
shrink_on_load = 2;
shrink = 1;
}
if (shrink_on_load > 1) {
// Recalculate integral shrink and double residual
factor = std::max(factor, 1.0);
shrink = floor(factor);
residual = shrink / factor;
// Reload input using shrink-on-load
g_object_unref(in);
in = vips_image_new();
if (baton->buffer_in_len > 1) {
@@ -138,26 +140,36 @@ void resize_async(uv_work_t *work) {
// Use vips_affine with the remaining float part using bilinear interpolation
VipsImage *affined = vips_image_new();
if (vips_affine(shrunk, &affined, residual, 0, 0, residual, "interpolate", vips_interpolate_bilinear_static(), NULL)) {
return resize_error(baton, shrunk);
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 (baton->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);
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 {
int left = (baton->width - affined->Xsize) / 2;
int top = (baton->height - affined->Ysize) / 2;
if (vips_embed(affined, &canvased, baton->embed, left, top, baton->width, baton->height, NULL)) {
return resize_error(baton, affined);
}
vips_copy(affined, &canvased, NULL);
}
g_object_unref(affined);
@@ -248,10 +260,10 @@ Handle<Value> resize(const Arguments& args) {
baton->crop = true;
} else if (canvas->Equals(String::NewSymbol("w"))) {
baton->crop = false;
baton->embed = 4;
baton->extend = VIPS_EXTEND_WHITE;
} else if (canvas->Equals(String::NewSymbol("b"))) {
baton->crop = false;
baton->embed = 0;
baton->extend = VIPS_EXTEND_BLACK;
}
baton->sharpen = args[6]->BooleanValue();
baton->progessive = args[7]->BooleanValue();

29
tests/parallel.js Executable file
View File

@@ -0,0 +1,29 @@
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() {});

View File

@@ -13,7 +13,7 @@ var outputJpg = __dirname + "/output.jpg";
var inputPng = __dirname + "/50020484-00001.png"; // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
var outputPng = __dirname + "/output.png";
var width = 640;
var width = 720;
var height = 480;
async.series({
@@ -39,7 +39,7 @@ async.series({
}).add("gm-file-file", {
defer: true,
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) {
throw err;
} else {
@@ -50,7 +50,7 @@ async.series({
}).add("gm-file-buffer", {
defer: true,
fn: function(deferred) {
gm(inputJpg).crop(width, height).quality(80).toBuffer(function (err, buffer) {
gm(inputJpg).resize(width, height).quality(80).toBuffer(function (err, buffer) {
if (err) {
throw err;
} else {
@@ -183,7 +183,7 @@ async.series({
}).add("gm-file-file", {
defer: true,
fn: function(deferred) {
gm(inputPng).crop(width, height).write(outputPng, function (err) {
gm(inputPng).resize(width, height).write(outputPng, function (err) {
if (err) {
throw err;
} else {
@@ -194,7 +194,7 @@ async.series({
}).add("gm-file-buffer", {
defer: true,
fn: function(deferred) {
gm(inputPng).crop(width, height).quality(80).toBuffer(function (err, buffer) {
gm(inputPng).resize(width, height).quality(80).toBuffer(function (err, buffer) {
if (err) {
throw err;
} else {