From 94b47508c0e8def8688d85d30b613cad93fa6955 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Mon, 24 Nov 2014 15:13:47 +0000 Subject: [PATCH] imagemagick-native now supports async and filter --- README.md | 12 ++-- test/bench/package.json | 2 +- test/bench/perf.js | 137 +++++++++++++++++++++++++--------------- 3 files changed, 94 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 710d739b..b19df432 100755 --- a/README.md +++ b/README.md @@ -10,13 +10,13 @@ The typical use case for this high speed Node.js module is to convert large images of many formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions. -The performance of JPEG resizing is typically 8x faster than ImageMagick and GraphicsMagick, based mainly on the number of CPU cores available. - -Memory usage is kept to a minimum, no child processes are spawned, everything remains non-blocking thanks to _libuv_ and Promises/A+ are supported. - This module supports reading and writing JPEG, PNG and WebP images to and from Streams, Buffer objects and the filesystem. It also supports reading images of many other types from the filesystem via libmagick++ or libgraphicsmagick++ if present. -When generating JPEG output all metadata is removed and Huffman tables optimised without having to use separate command line tools like [jpegoptim](https://github.com/tjko/jpegoptim) and [jpegtran](http://jpegclub.org/jpegtran/). +Only small regions of uncompressed image data are held in memory and processed at a time, taking full advantage of multiple CPU cores and L1/L2/L3 cache. Resizing an image is typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings. + +Huffman tables are optimised when generating JPEG output images without having to use separate command line tools like [jpegoptim](https://github.com/tjko/jpegoptim) and [jpegtran](http://jpegclub.org/jpegtran/). PNG filtering can be disabled, which for diagrams and line art often produces the same result as [pngcrush](http://pmt.sourceforge.net/pngcrush/). + +Everything remains non-blocking thanks to _libuv_, no child processes are spawned and Promises/A+ are supported. Anyone who has used the Node.js bindings for [GraphicsMagick](https://github.com/aheckmann/gm) will find the API similarly fluent. @@ -548,7 +548,7 @@ sudo yum install -y --enablerepo=epel GraphicsMagick ### The contenders -* [imagemagick-native](https://github.com/mash/node-imagemagick-native) v1.2.2 - Supports Buffers only and blocks main V8 thread whilst processing. +* [imagemagick-native](https://github.com/mash/node-imagemagick-native) v1.2.2 - Supports Buffers only * [imagemagick](https://github.com/yourdeveloper/node-imagemagick) v0.1.3 - Supports filesystem only and "has been unmaintained for a long time". * [gm](https://github.com/aheckmann/gm) v1.16.0 - Fully featured wrapper around GraphicsMagick. * sharp v0.6.2 - Caching within libvips disabled to ensure a fair comparison. diff --git a/test/bench/package.json b/test/bench/package.json index 29aec7b0..fdda7df6 100755 --- a/test/bench/package.json +++ b/test/bench/package.json @@ -9,7 +9,7 @@ }, "devDependencies": { "imagemagick": "^0.1.3", - "imagemagick-native": "^1.5.0", + "imagemagick-native": "^1.6.0", "gm": "^1.17.0", "async": "^0.9.0", "semver": "^4.1.0", diff --git a/test/bench/perf.js b/test/bench/perf.js index 9abd14ed..b08b4864 100755 --- a/test/bench/perf.js +++ b/test/bench/perf.js @@ -17,6 +17,9 @@ var fixtures = require('../fixtures'); var width = 720; var height = 480; +// Approximately equivalent to fast bilinear +var magickFilter = 'Triangle'; + // Disable libvips cache to ensure tests are as fair as they can be sharp.cache(0); @@ -31,7 +34,9 @@ async.series({ dstPath: fixtures.outputJpg, quality: 0.8, width: width, - height: height + height: height, + format: 'jpg', + filter: magickFilter }, function(err) { if (err) { throw err; @@ -48,55 +53,78 @@ async.series({ quality: 80, width: width, height: height, - format: 'JPEG' + format: 'JPEG', + filter: magickFilter + }, function (err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } }); - deferred.resolve(); } }).add('gm-buffer-file', { defer: true, fn: function(deferred) { - gm(inputJpgBuffer).resize(width, height).quality(80).write(fixtures.outputJpg, function (err) { - if (err) { - throw err; - } else { - deferred.resolve(); - } - }); + gm(inputJpgBuffer) + .resize(width, height) + .filter(magickFilter) + .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).quality(80).toBuffer(function (err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + gm(inputJpgBuffer) + .resize(width, height) + .filter(magickFilter) + .quality(80) + .toBuffer(function (err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('gm-file-file', { defer: true, fn: function(deferred) { - gm(fixtures.inputJpg).resize(width, height).quality(80).write(fixtures.outputJpg, function (err) { - if (err) { - throw err; - } else { - deferred.resolve(); - } - }); + gm(fixtures.inputJpg) + .resize(width, height) + .filter(magickFilter) + .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).quality(80).toBuffer(function (err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + gm(fixtures.inputJpg) + .resize(width, height) + .filter(magickFilter) + .quality(80) + .toBuffer(function (err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-buffer-file', { defer: true, @@ -359,7 +387,9 @@ async.series({ srcPath: fixtures.inputPng, dstPath: fixtures.outputPng, width: width, - height: height + height: height, + format: 'jpg', + filter: magickFilter }, function(err) { if (err) { throw err; @@ -375,32 +405,39 @@ async.series({ srcData: inputPngBuffer, width: width, height: height, - format: 'PNG' + format: 'PNG', + filter: magickFilter }); deferred.resolve(); } }).add('gm-file-file', { defer: true, fn: function(deferred) { - gm(fixtures.inputPng).resize(width, height).write(fixtures.outputPng, function (err) { - if (err) { - throw err; - } else { - deferred.resolve(); - } - }); + gm(fixtures.inputPng) + .resize(width, height) + .filter(magickFilter) + .write(fixtures.outputPng, function (err) { + if (err) { + throw err; + } else { + deferred.resolve(); + } + }); } }).add('gm-file-buffer', { defer: true, fn: function(deferred) { - gm(fixtures.inputPng).resize(width, height).quality(80).toBuffer(function (err, buffer) { - if (err) { - throw err; - } else { - assert.notStrictEqual(null, buffer); - deferred.resolve(); - } - }); + gm(fixtures.inputPng) + .resize(width, height) + .filter(magickFilter) + .toBuffer(function (err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); } }).add('sharp-buffer-file', { defer: true,