diff --git a/README.md b/README.md index d959e7b..792cbe2 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ Resized images by specifying `width` and `height`. There are three resizing styl * `aspectfill`: Default. The resulting image will be exactly the specified size, and may be cropped. * `aspectfit`: Scales the image so that it will not have to be cropped. + * `aspectwithbg`: Scales the image and fill the rest by background color. * `fill`: Squishes or stretches the image so that it fills exactly the specified size. ```js @@ -94,8 +95,9 @@ fs.writeFileSync('after_resize.jpg', imagemagick.convert({ srcData: fs.readFileSync('before_resize.jpg'), width: 100, height: 100, - resizeStyle: 'aspectfill', // is the default, or 'aspectfit' or 'fill' - gravity: 'Center' // optional: position crop area when using 'aspectfill' + resizeStyle: 'aspectfill', // is the default, or 'aspectfit', 'aspectwithbg' or 'fill' + gravity: 'Center', // optional: position crop area when using 'aspectfill' + background: '#4d4d4d' // optional: background color when using 'aspectwithbg' })); ``` @@ -105,9 +107,9 @@ Original: Resized: -aspectfill | aspectfit | fill -:---: | :---: | :---: -![alt text](http://elad.github.io/node-imagemagick-native/examples/resize_aspectfill.jpg 'aspectfill') | ![alt text](http://elad.github.io/node-imagemagick-native/examples/resize_aspectfit.jpg 'aspectfit') | ![alt text](http://elad.github.io/node-imagemagick-native/examples/resize_fill.jpg 'fill') +aspectfill | aspectfit | aspectwithbg | fill +:---: | :---: | :---: | :---: +![alt text](http://elad.github.io/node-imagemagick-native/examples/resize_aspectfill.jpg 'aspectfill') | ![alt text](http://elad.github.io/node-imagemagick-native/examples/resize_aspectfit.jpg 'aspectfit') | ![alt text](http://horiuchi.github.io/node-imagemagick-native/examples/resize_aspectwithbg.jpg 'aspectwithbg') | ![alt text](http://elad.github.io/node-imagemagick-native/examples/resize_fill.jpg 'fill') *Image courtesy of [Christoph](https://www.flickr.com/photos/scheinwelten/381994831).* @@ -158,13 +160,15 @@ The `options` argument can have following values: width: optional. px. height: optional. px. density optional. Integer dpi value to convert - resizeStyle: optional. default: 'aspectfill'. can be 'aspectfit', 'fill' - aspectfill: keep aspect ratio, get the exact provided size. - aspectfit: keep aspect ratio, get maximum image that fits inside provided size - fill: forget aspect ratio, get the exact provided size + resizeStyle: optional. default: 'aspectfill'. can be 'aspectfit', 'aspectwithbg', 'fill' + aspectfill: keep aspect ratio, get the exact provided size. + aspectfit: keep aspect ratio, get maximum image that fits inside provided size + aspectwithbg: keep aspect ratio, get the exact proviede size and no cropped. + fill: forget aspect ratio, get the exact provided size gravity: optional. default: 'Center'. used to position the crop area when resizeStyle is 'aspectfill' can be 'NorthWest', 'North', 'NorthEast', 'West', 'Center', 'East', 'SouthWest', 'South', 'SouthEast', 'None' + background: optional. default: 'Transpearental'. used to background color when resizeStle is 'aspectwithbg' format: optional. output format, ex: 'JPEG'. see below for candidates filter: optional. resize filter. ex: 'Lagrange', 'Lanczos'. see below for candidates blur: optional. ex: 0.8 diff --git a/src/imagemagick.cc b/src/imagemagick.cc index e0a5b9e..d57e0e4 100644 --- a/src/imagemagick.cc +++ b/src/imagemagick.cc @@ -87,6 +87,7 @@ struct convert_im_ctx : im_ctx_base { std::string format; std::string filter; std::string blur; + std::string background; unsigned int quality; int rotate; int density; @@ -179,6 +180,25 @@ void DoConvert(uv_work_t* req) { Magick::Image image; + if ( ! context->background.empty() ) { + const char* background = context->background.c_str(); + try { + Magick::Color bg(background); + image.backgroundColor(bg); + if (debug) printf( "set background: %s\n", background ); + } + catch (std::exception& err) { + std::string message = "image.backgroundColor failed with error: "; + message += err.what(); + context->error = message; + return; + } + catch (...) { + context->error = std::string("unhandled error"); + return; + } + } + if ( !ReadImageMagick(&image, srcBlob, context->srcFormat, context) ) return; @@ -382,6 +402,58 @@ void DoConvert(uv_work_t* req) { return; } } + else if ( strcmp ( resizeStyle, "aspectwithbg" ) == 0 ) { + // keep aspect ratio, get the maximum image which fits inside specified size + char geometryString[ 32 ]; + sprintf( geometryString, "%dx%d", width, height ); + if (debug) printf( "resize to: %s\n", geometryString ); + + Magick::Image compositeImage(image); + try { + compositeImage.zoom( geometryString ); + } + catch (std::exception& err) { + std::string message = "image.resize failed with error: "; + message += err.what(); + context->error = message; + return; + } + catch (...) { + context->error = std::string("unhandled error"); + return; + } + + sprintf( geometryString, "%dx%d!", width, height ); + if (debug) printf( "set background to: %s\n", geometryString ); + try { + image.erase(); + image.zoom( geometryString ); + } + catch (std::exception& err) { + std::string message = "image.initialize failed with error: "; + message += err.what(); + context->error = message; + return; + } + catch (...) { + context->error = std::string("unhandled error"); + return; + } + + try { + image.composite(compositeImage, Magick::CenterGravity, Magick::OverCompositeOp); + } + catch (std::exception& err) { + std::string message = "image.composite failed with error: "; + message += err.what(); + context->error = message; + return; + } + catch (...) { + context->error = std::string("unhandled error"); + return; + } + } else { context->error = std::string("resizeStyle not supported"); return; @@ -556,6 +628,10 @@ NAN_METHOD(Convert) { context->filter = !filterValue->IsUndefined() ? *String::Utf8Value(filterValue) : ""; + Local backgroundValue = obj->Get( Nan::New("background").ToLocalChecked() ); + context->background = !backgroundValue->IsUndefined() ? + *String::Utf8Value(backgroundValue) : ""; + uv_work_t* req = new uv_work_t(); req->data = context; if(!isSync) { diff --git a/test/benchmark.js b/test/benchmark.js index 934fe0a..0bf246a 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -19,7 +19,7 @@ function resize (callback) { }, function (err,stdout,stderr) { assert( stdout.length > 0 ); // console.log( "im length: "+stdout.length ); - // require('fs').writeFileSync( "./test/out.fork.jpg", stdout, 'binary' ); + // require('fs').writeFileSync( "out.fork.jpg", stdout, 'binary' ); callback(); }); } @@ -38,7 +38,7 @@ function resize_native (callback) { }); assert( stdout.length > 0 ); // console.log( "im_native length: "+stdout.length ); - // require('fs').writeFileSync( "./test/out.native.jpg", stdout, 'binary' ); + // require('fs').writeFileSync( "out.native.jpg", stdout, 'binary' ); callback(); } diff --git a/test/leak.js b/test/leak.js index 7db04d5..5886bd1 100644 --- a/test/leak.js +++ b/test/leak.js @@ -9,7 +9,7 @@ memwatch.on( 'leak', function( info ) { memwatch.on('stats', function(stats) { console.log( "stats: ", stats ); }); -var srcData = require('fs').readFileSync( "./test/test.jpg" ); +var srcData = require('fs').readFileSync( "test.jpg" ); var hd = new memwatch.HeapDiff(); diff --git a/test/test.js b/test/test.js index aa58f12..e4327ac 100644 --- a/test/test.js +++ b/test/test.js @@ -226,6 +226,44 @@ test( 'convert jpg -> jpg aspectfit', function (t) { t.end(); }); +test( 'convert png -> png aspectwithbg', function (t) { + var buffer = imagemagick.convert({ + srcData: require('fs').readFileSync( "test.png" ), // 58x66 + width: 100, + height: 100, + resizeStyle: 'aspectwithbg', + background: '#4d4d4d', + quality: 80, + format: 'PNG', + debug: debug + }); + t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); + var info = imagemagick.identify({srcData: buffer }); + t.equal( info.width, 100 ); + t.equal( info.height, 100 ); + saveToFileIfDebug( buffer, "out.png-aspectwithbg.png" ); + t.end(); +}); + +test( 'convert png.wide -> png.wide aspectwithbg', function (t) { + var buffer = imagemagick.convert({ + srcData: require('fs').readFileSync( "test.wide.png" ), // 66x58 + width: 100, + height: 100, + resizeStyle: 'aspectwithbg', + background: '#4d4d4d', + quality: 80, + format: 'PNG', + debug: debug + }); + t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); + var info = imagemagick.identify({srcData: buffer }); + t.equal( info.width, 100 ); + t.equal( info.height, 100 ); + saveToFileIfDebug( buffer, "out.wide.png-aspectwithbg.png" ); + t.end(); +}); + test( 'convert broken png', function (t) { var srcData = require('fs').readFileSync( "broken.png" ) , buffer;