From be8f35d830c93ed53b4ae970cb8ad1000f989506 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Thu, 16 Jan 2014 22:51:44 +0000 Subject: [PATCH] Use shrink-on-load for JPEG images, partially implementing #4. Switch to new vip_ methods from legacy im_ methods. Large performance gains all round. --- README.md | 10 ++--- package.json | 2 +- src/sharp.cc | 105 ++++++++++++++++++++++++++++++-------------------- tests/perf.js | 4 -- 4 files changed, 69 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index d59887f8..c9e3d937 100755 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ It is somewhat opinionated in that it only deals with JPEG and PNG images, alway 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. -Performance is 4x-8x faster than ImageMagick and 2x-4x faster than GraphicsMagick, based mainly on the number of CPU cores available. +Performance is 12x-15x faster than ImageMagick and 4x-6x faster than GraphicsMagick, based mainly on the number of CPU cores available. ## Prerequisites @@ -134,15 +134,15 @@ Test environment: * imagemagick x 5.53 ops/sec ±0.55% (31 runs sampled) * 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) +* sharp-file x 72.01 ops/sec ±7.19% (74 runs sampled) +* sharp-buffer x 75.73 ops/sec ±0.44% (75 runs sampled) #### PNG * imagemagick x 4.65 ops/sec ±0.37% (27 runs sampled) * gm x 21.65 ops/sec ±0.18% (56 runs sampled) -* sharp-file x 39.47 ops/sec ±6.78% (68 runs sampled) -* sharp-buffer x 42.87 ops/sec ±0.19% (71 runs sampled) +* sharp-file x 43.80 ops/sec ±6.81% (75 runs sampled) +* sharp-buffer x 45.67 ops/sec ±0.41% (75 runs sampled) ## Licence diff --git a/package.json b/package.json index 1a94e911..c4926b7e 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sharp", - "version": "0.0.8", + "version": "0.0.9", "author": "Lovell Fuller", "description": "High performance module to resize JPEG and PNG images using the libvips image processing library", "scripts": { diff --git a/src/sharp.cc b/src/sharp.cc index 68ec36a7..b031ac40 100755 --- a/src/sharp.cc +++ b/src/sharp.cc @@ -1,31 +1,12 @@ #include +#include #include #include -#include #include -#include using namespace v8; using namespace node; -// Free VipsImage children when object goes out of scope -// Thanks due to https://github.com/dosx/node-vips -class ImageFreer { - public: - ImageFreer() {} - ~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 v_; -}; - struct ResizeBaton { std::string src; std::string dst; @@ -45,13 +26,21 @@ bool EndsWith(std::string const &str, std::string const &end) { return str.length() >= end.length() && 0 == str.compare(str.length() - end.length(), end.length(), end); } +bool IsJpeg(std::string const &str) { + return EndsWith(str, ".jpg") || EndsWith(str, ".jpeg"); +} + +bool IsPng(std::string const &str) { + return EndsWith(str, ".png"); +} + void ResizeAsync(uv_work_t *work) { ResizeBaton* baton = static_cast(work->data); - VipsImage *in = vips_image_new_mode((baton->src).c_str(), "p"); - if (EndsWith(baton->src, ".jpg") || EndsWith(baton->src, ".jpeg")) { + VipsImage *in = vips_image_new(); + if (IsJpeg(baton->src)) { vips_jpegload((baton->src).c_str(), &in, NULL); - } else if (EndsWith(baton->src, ".png")) { + } else if (IsPng(baton->src)) { vips_pngload((baton->src).c_str(), &in, NULL); } else { (baton->err).append("Unsupported input file type"); @@ -62,36 +51,66 @@ void ResizeAsync(uv_work_t *work) { 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; - } - - double xfactor = static_cast(img->Xsize) / std::max(baton->cols, 1); - double yfactor = static_cast(img->Ysize) / std::max(baton->rows, 1); + double xfactor = static_cast(in->Xsize) / std::max(baton->cols, 1); + double yfactor = static_cast(in->Ysize) / std::max(baton->rows, 1); double factor = baton->crop ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor); factor = std::max(factor, 1.0); int shrink = floor(factor); double residual = shrink / factor; - // Use im_shrink with the integral reduction - if (im_shrink(img, t[0], shrink, shrink)) { + // Try to use libjpeg shrink-on-load + int shrink_on_load = 1; + if (IsJpeg(baton->src)) { + if (shrink >= 8) { + residual = residual * shrink / 8; + shrink_on_load = 8; + shrink = 1; + } else if (shrink >= 4) { + residual = residual * shrink / 4; + shrink_on_load = 4; + shrink = 1; + } else if (shrink >= 2) { + residual = residual * shrink / 2; + shrink_on_load = 2; + shrink = 1; + } + if (shrink_on_load > 1) { + if (vips_jpegload((baton->src).c_str(), &in, "shrink", shrink_on_load, NULL)) { + (baton->err).append(vips_error_buffer()); + vips_error_clear(); + g_object_unref(in); + return; + } + } + } + + 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(); + g_object_unref(in); return; } - // Use im_affinei with the remaining float part using bilinear interpolation - if (im_affinei_all(t[0], t[1], vips_interpolate_bilinear_static(), residual, 0, 0, residual, 0, 0)) { + if (shrink > 1) { + // Use vips_shrink with the integral reduction + if (vips_shrink(img, &t[0], shrink, shrink, NULL)) { + (baton->err).append(vips_error_buffer()); + vips_error_clear(); + g_object_unref(in); + return; + } + } else { + t[0] = img; + } + + // Use vips_affine with the remaining float part using bilinear interpolation + if (vips_affine(t[0], &t[1], residual, 0, 0, residual, "interpolate", vips_interpolate_bilinear_static(), NULL)) { (baton->err).append(vips_error_buffer()); vips_error_clear(); + g_object_unref(in); return; } img = t[1]; @@ -104,6 +123,7 @@ void ResizeAsync(uv_work_t *work) { if (im_extract_area(img, t[2], left, top, width, height)) { (baton->err).append(vips_error_buffer()); vips_error_clear(); + g_object_unref(in); return; } img = t[2]; @@ -113,6 +133,7 @@ void ResizeAsync(uv_work_t *work) { if (im_embed(img, t[2], baton->embed, left, top, baton->cols, baton->rows)) { (baton->err).append(vips_error_buffer()); vips_error_clear(); + g_object_unref(in); return; } img = t[2]; @@ -127,6 +148,7 @@ void ResizeAsync(uv_work_t *work) { if (im_conv(img, t[3], sharpen)) { (baton->err).append(vips_error_buffer()); vips_error_clear(); + g_object_unref(in); return; } img = t[3]; @@ -136,14 +158,12 @@ void ResizeAsync(uv_work_t *work) { 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 @@ -160,6 +180,7 @@ void ResizeAsync(uv_work_t *work) { } else { (baton->err).append("Unsupported output file type"); } + g_object_unref(in); vips_thread_shutdown(); } diff --git a/tests/perf.js b/tests/perf.js index 7a51d84e..01851372 100755 --- a/tests/perf.js +++ b/tests/perf.js @@ -8,11 +8,9 @@ var Benchmark = require("benchmark"); var inputJpg = __dirname + "/2569067123_aca715a2ee_o.jpg"; // http://www.flickr.com/photos/grizdave/2569067123/ 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 outputPng = __dirname + "/output.png"; -var outputPngLength = 60380; var width = 640; var height = 480; @@ -73,7 +71,6 @@ async.series({ throw err; } else { assert.notStrictEqual(null, buffer); - assert.strictEqual(outputJpgLength, buffer.length); deferred.resolve(); } }); @@ -131,7 +128,6 @@ async.series({ throw err; } else { assert.notStrictEqual(null, buffer); - assert.strictEqual(outputPngLength, buffer.length); deferred.resolve(); } });