5 October 2011
I’ve been playing around with Node.js a little more recently and wanted to share a little short lesson I had. A quick little break from the phone client…
As part of the cloud environment for my application, I have a service that dynamically resizes a given image from Foursquare into a Windows Phone tile size (173x173 pixels). This is used for the “pin to start” feature of 4th and Mayor.
The initial implementation of this service was done in PHP.
However, the new version of my cloud infrastructure is powered by Node.js for the most part, and I ran into some fun problems trying to get ImageMagick to properly resize images to be aspect-aware. I am using Express for servicing notifications and other endpoints.
Here’s an image from a Foursquare user of Microsoft Building 40, where my office is right now. I like to keep it pinned to my start screen, and the source image from fsq is actually much larger and not properly sized or cropped for tile display.
When the pinned tile is created, the remote URI points to the 4thandmayor.com cloud presence, and then server-side I resize the Foursquare image provided in the query string. (I also validate that it’s actually an image from there, etc.)
I’m wanting to use the node-imagemagick module with Node to do the resizing (similar to what PHP was doing with ImageMagick before). It really just shells out to the ImageMagick ‘convert’ program.
There is a built-in “resize” function, however I don’t exactly want to use that: here’s what happens when I resize the image the standard way. I just assumed that it would figure out a good size and crop. It’s been a while since I used this stuff in college!
var im = require("imagemagick"); im.resize({ srcData : img, strip : false, width : 173, height : 173 }, function(err, stdout, stderr) { if (err){ redirectToStandardTile(res); } else { res.contentType("image/jpeg"); res.end(stdout, 'binary'); } });
The aspect ratio is maintained.
There’s a separate crop function as well, but it works only on files, no streams. I’d like to not use temporary files if I can and instead just pipe around the raw image bytes. There is also a thumbnail command built into ImageMagick, but I don’t want to reinvent the wheel and not be able to use the helpful node library created by Rasmus.
You can also force to be a specific size, such as this, by appending an exclamation point to the height (as a string literal). Not good for me (the photo is stretched):
var im = require("imagemagick"); im.resize({ srcData : img, strip : false, width : 173, height : "173!" // force the sizing. }, function(err, stdout, stderr) { if (err){ redirectToStandardTile(res); } else { res.contentType("image/jpeg"); res.end(stdout, 'binary'); } });
Instead, I decided to append custom arguments for ImageMagick’s convert program, and finally came up with a combination of extent values, etc., that work for my needs. Here’s what finally worked for me:
// Resize a photo for use in a live tile or secondary place tile. app.get('/tile.php', function (req, res) { var original = req.param("i"); if (original) { getImage(original, function (err, img) { if (!err && img) { var im = require("imagemagick"); im.resize({ srcData : img, strip : false, width : 173, height : "173^", customArgs: [ "-gravity", "center" ,"-extent", "173x173" ] }, function(err, stdout, stderr) { if (err){ redirectToStandardTile(res); } else { res.contentType("image/jpeg"); res.end(stdout, 'binary'); } }); } else { redirectToStandardTile(res); } }); } }); function redirectToStandardTile(res) { res.redirect("http://www.4thandmayor.com/app/genericTile.png"); } function getImage(uri, callback) { // (err, res) request( { encoding: 'binary', uri: uri, }, function (error, response, body) { if (!error && response.statusCode == 200) { callback(null, body); } else { callback(error, null); } }); }
This implementation includes the Express code, in case you were wondering about doing that part. I’m attaching to the same former PHP URI for this functionality so that I don’t have to update paths and can continue to serve older versions of the app as appropriate.
Hope this helps.
Jeff Wilcox is a Software Engineer at Microsoft in the Open Source Programs Office (OSPO), helping Microsoft engineers use, contribute to and release open source at scale.