Scale and Center Images with Imagemagick

Handling user-uploaded images can be a pain in the ass - especially when you have image requirements. My standard procedure used to involve loading these massive images into a cropping plugin with a set aspect ratio, but I like to avoid this when I can.

Recently, I started to experiment with a new way to handle these uploads without any user intervention. It isn't perfect and isn't always the right solution, but it's a hell of a lot cleaner.

The idea is to scale the image down just enough to fit inside the available space while preserving the aspect ratio, center it, and make any excess remaining space transparent (or a color to match the background of the document).

Let's put that into action. Let's say we have a 300x300px space available to fit our image...

...and an image with a troublesome aspect ratio...

Here is the ImageMagick command in all it's glory...

convert inputImage.jpg -scale 300x300 png:- | convert - -gravity Center -background black -auto-orient -extent 300x300 done.png

Let's walk through this real quick:

  1. First we run the scale command, passing our dimensions. This will scale our image to fit inside the area without losing our aspect ratio. The png:- format will pipe the image to stdout as a png.
  2. For the second command, we again run convert, using - to pass the image to stdin.
  3. The gravity option will center the image for us. You can also align the image to the left or right with West or East.
  4. The background option obviously sets our background color and auto-orient makes sure the image is oriented correctly.
  5. The extent option will size the image to the given area, while allowing the background to change color.

Here is the final image (I made the background black so you can see the output).

Of course, in a web app we need to translate this to something we can use on the server. Here is how it looks in express.js, using the excellent gm library:

const gm = require('gm').subClass({  
    imageMagick: true,
});
const height = 300;  
const width = 300;

new Promise((resolve, reject) =>
    gm(req.file.buffer)
        .scale(width, height)
        .toBuffer('PNG', (err, buffer) => {
            if (err) reject(err);
            resolve(buffer);
        })
)
    .then(bufferedImage =>
        new Promise((resolve, reject) =>
            gm(bufferedImage)
                .gravity('Center')
                .background('black')
                .autoOrient()
                .extent(width, height)
                .toBuffer('PNG', (err, buffer) => {
                    if (err) reject(err);
                    resolve(buffer);
                })
        );
    )
    .then(finishedImage => {
        // DO STUFF WITH FINISHED IMAGE HERE
    })
    .catch(next);

Also, you should probably check that the user's image isn't smaller than the predefined dimensions. If that's the case, I would probably warn the user or maybe just skip the scale command.

Justin Williamson

Read more posts by this author.

USA