diff --git a/README.md b/README.md index b9a3773d..9d04c063 100755 --- a/README.md +++ b/README.md @@ -143,6 +143,10 @@ Scale to `width` x `height`. By default, the resized image is cropped to the exa Crop the resized image to the exact size specified, the default behaviour. +### max() + +Preserving aspect ratio, resize the image to the maximum width or height specified. + ### embedWhite() Embed the resized image on a white background of the exact size specified. diff --git a/index.js b/index.js index 05b1228b..5f758544 100755 --- a/index.js +++ b/index.js @@ -44,6 +44,12 @@ Sharp.prototype.embedBlack = function() { return this; }; +Sharp.prototype.max = function() { + this.options.canvas = 'm'; + return this; +}; + + Sharp.prototype.sharpen = function(sharpen) { this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true; return this; diff --git a/src/sharp.cc b/src/sharp.cc index 41704956..3ae31164 100755 --- a/src/sharp.cc +++ b/src/sharp.cc @@ -20,6 +20,7 @@ struct resize_baton { int width; int height; bool crop; + bool max; VipsExtend extend; bool sharpen; bool progressive; @@ -28,7 +29,7 @@ struct resize_baton { int compressionLevel; std::string err; - resize_baton(): buffer_in_len(0), buffer_out_len(0) {} + resize_baton(): buffer_in_len(0), buffer_out_len(0), crop(false), max(false), sharpen(false), progressive(false) {} }; typedef enum { @@ -138,6 +139,14 @@ class ResizeWorker : public NanAsyncWorker { double xfactor = static_cast(in->Xsize) / static_cast(baton->width); double yfactor = static_cast(in->Ysize) / static_cast(baton->height); factor = baton->crop ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor); + // if max is set, we need to compute the real size of the thumb image + if(baton->max) { + if(xfactor > yfactor) { + baton->height = round(in->Ysize/factor); + } else { + baton->width = round(in->Xsize/factor); + } + } } else if (baton->width > 0) { // Fixed width, auto height factor = static_cast(in->Xsize) / static_cast(baton->width); @@ -226,7 +235,7 @@ class ResizeWorker : public NanAsyncWorker { // Crop/embed VipsImage *canvased = vips_image_new(); if (affined->Xsize != baton->width || affined->Ysize != baton->height) { - if (baton->crop) { + if (baton->crop || baton->max) { // Crop int width = std::min(affined->Xsize, baton->width); int height = std::min(affined->Ysize, baton->height); @@ -351,6 +360,9 @@ NAN_METHOD(resize) { } else if (canvas->Equals(NanSymbol("b"))) { baton->crop = false; baton->extend = VIPS_EXTEND_BLACK; + } else if (canvas->Equals(NanSymbol("m"))) { + baton->crop = false; + baton->max = true; } baton->sharpen = args[6]->BooleanValue(); baton->progressive = args[7]->BooleanValue(); diff --git a/tests/unit.js b/tests/unit.js index 4d8c7774..2697cc21 100755 --- a/tests/unit.js +++ b/tests/unit.js @@ -109,6 +109,29 @@ async.series([ done(); }); }); + }, + // Resize to max width or height considering ratio (landscape) + function(done) { + sharp(inputJpg).resize(320,320).max().write(outputJpg, function(err) { + if (err) throw err; + imagemagick.identify(outputJpg, function(err, features) { + if (err) throw err; + assert.strictEqual(320, features.width); + assert.strictEqual(261, features.height); + done(); + }); + }); + }, + // Resize to max width or height considering ratio (portrait) + function(done) { + sharp(inputTiff).resize(320,320).max().write(outputJpg, function(err) { + if (err) throw err; + imagemagick.identify(outputJpg, function(err, features) { + if (err) throw err; + assert.strictEqual(243, features.width); + assert.strictEqual(320, features.height); + done(); + }); + }); } - ]);