Working with Images in Node.js - GraphicsMagick and ImageMagick

Introduction

As images have become an integral part of the web, the need for image processing becomes ever-present. There are various libraries and binaries that are used for image processing in Node.js, two of which are GraphicsMagick and ImageMagick.

ImageMagick is an open-source image processing software for creating, modifying, and conversion of images. GraphicsMagick is a similar tool that was originally a fork of the ImageMagick project that has become an independent project of its own with several improvements.

Some of the advantages GraphicsMagick has over ImageMagick include more efficiency, a smaller size, fewer security exploits and is generally more stable than ImageMagick. They are both available for use in Node.js as NPM packages: GraphicsMagick and ImageMagick.

Installing GraphicsMagick and ImageMagick

Before installing either of these packages, you have to download and install the command-line interface (CLI) tools on your system. You can also use ImageMagick directly from GraphicsMagick.

If you plan to use the GraphicsMagick module, you can either install the ImageMagick or GraphicsMagick CLI tools. If instead, you want to use ImageMagick, you need to install the ImageMagick CLI tool.

After downloading and installing the required CLI tool, you can check the version of your installation by running the following commands on your terminal.

For ImageMagick:

$ convert -version

If it has been successfully installed, this will print out details of the installed software to the terminal.

For GraphicsMagick:

$ gm convert logo: logo.miff
$ gm convert logo.miff win:

Likewise, if the installation was successful, this will display the GraphicsMagick logo in a window.

Next, to add the module to your project, we'll use npm.

For ImageMagick:

$ npm install imagemagick

For GraphicsMagick:

$ npm install gm

Image Processing in Node.js With GraphicsMagick

In this tutorial, we will be learning how to work with images in Node.js using both GraphicsMagick and ImageMagick. In this section, we will start with GraphicsMagick.

Create a folder called node-graphics-magick, cd into the folder, initialize the Node project with default settings, and install GraphicsMagick as shown below:

$ mkdir node-graphics-magick
$ cd node-graphics-magick
$ npm init -y
$ npm install gm

Next, open the folder with your favorite code editor and create an index.js file. To use the module in the project, import it using require(). We will also add the native file system (fs) module to help us interact with the file system:

const fs = require('fs');
const gm = require('gm');

You also want to add an image to your folder that will be used in this tutorial, we'll name it sample_image.

As mentioned before, the GraphicsMagick module allows you to use ImageMagick as well. If you want to use this option, you'd need to import the subclass:

const gm = require('gm').subClass({imageMagick: true});

Calling the gm Constructor

There are three ways in which you can call the gm constructor method.

  1. By passing the path to the image as a string argument:
const gm = require('gm');

gm('sample_image');
  1. You can also pass a ReadableStream or Buffer as the first argument, with an optional filename for format inference:
const gm = require('gm');

// ReadableStream
let readableImageFileStream = fs.createReadStream('sample_image1.jpeg');
gm(readableImageFileStream, sample_image);

// Buffer
let imageFileBuffer = fs.readFileSync('sample_image1.jpeg');
gm(imageFileBuffer);
  1. Another option is to pass two integers for width and height with an optional background color to create a new image on the fly:
const gm = require('gm');

gm(300, 300, "#00ff55aa");

Getting Image Information

GraphicsMagick provides several getters for retrieving information about images. One is the identify() method. It returns all image data available:

const gm = require('gm');

gm("sample_image.jpg").identify(function(err, value) {
    console.log(value);

    if(err) {
        console.log(err);
    }
});

After including the gm module, we called the gm() constructor and then called the identify() function by passing a function that takes two arguments - err for getting any error that occurs and value that contains the image information.

We're printing out the value to our console and checking if there's an error. If an error occurred, it will be logged to the console. You can use that block to catch errors and display appropriate messages in real life situations.

Note that the value could be undefined.

Running this code will return an object with all image data available for the image. Here is what the output looks like for an image we used:

{
  Format: 'JPEG (Joint Photographic Experts Group JFIF format)',
  format: 'JPEG',
  Geometry: '213x133',
  size: { width: 213, height: 133 },
  Class: 'DirectClass',
  Type: 'true color',
  Depth: '8 bits-per-pixel component',
  ...
  Signature: 'ae5b5e492457ac667e9a4cb1e7b78b7e6459fbf342ea741857ee4e9e1092ad73',
  Tainted: 'False',
  path: 'sample_image.jpg'
}

It provides information about the size, resolution, file size and image color depth amongst others. If you want to get some of these details individually, there are separate function to get them such as: size(), format(), depth(), orientation(), res(), etc.

They all have the same syntax as the identify() function. For example, if we wanted to retrieve the size of an image, we would use:

const gm = require('gm');

gm("sample_image.png").size(function(err, value) {
    console.log(value);
    if(err) {
        console.log(err);
    }
});

Resizing Images

It is also common to resize images. The syntax for the resize() functions is:

resize(width, [, height [, options]])

Where the height and options parameters are optional. The options parameter can either be %, @, !, < or >. We'll cover each of these.

The width and height specified are the maximum values allowed for both properties.

Without specifying any options, GraphicsMagick will maintain the aspect ratio of the original image. If both width and height are given, the image will be resized until it reaches the maximum value for one or the other.

However, you can force the module to resize to the given width and height by using the ! option.

We also use the write() function to save the output to a new file.

Resizing an image to a particular width while maintaining the aspect ratio:

const gm = require('gm');

gm("sample_image.jpg")
    .resize(200)
    .write('resized_img_width_only.jpg', function (err) {
        if(err) console.log(err);
        console.log("Done!")
    });

We can also resize an image until its size reaches either the maximum width or height while maintaining the aspect ration:

const gm = require('gm');

gm("sample_image.jpg")
    .resize(200, 200)
    .write('resized_img_widthandheight.jpg', function (err) {
        if(err) console.log(err);
        console.log("Done!")
    })

Or, we can resize an image to fit the exact width and height, possibly changing the original aspect ratio:

const gm = require('gm');

gm("sample_image.jpg")
    .resize(600, 200, '!')
    .write('resized_to_fit.jpg', function (err) {
        if(err) console.log(err);
        console.log("Done!")
    });

We can also use percentages instead of pixels for resizing images, i.e. we can resize the image to 50% of its original size:

const gm = require('gm');

gm("img.png")
    .resize(50, 50, "%")
    .write('resized_img_percentage.jpg', function (err) {
        if(err) console.log(err);
        console.log("Done!")
    });

Of course, you can also give a percentage higher than 100% to make the image larger.

Another interesting option you can use is @, which resizes the image in such a way that it takes up at most width*height pixels. This means, that if you write something like this:

const gm = require('gm');

gm("sample_image.jpg")
    .resize(100, 100, "@")
    .write('resized_img_max_area_in_pixels.jpg', function (err) {
        if(err) console.log(err);
        console.log("Done!")
    });

The image will be resized so that it fits an area of 10.000 pixels (100 * 100 = 10.000 pixels). In other words, if we multiply the width and height of the resulting image, it will be a number less than or equal to 10.000.

Now we'll take at the > and < options:

  • The > option resizes an image only if the width and height of the given image are greater than the given width and height.
  • The < option resizes an image only if the width and height of the given image are smaller than the given width and height.
const gm = require('gm');

// Resize image if image width is greater than 100 and image height is greater than 100
gm("sample_image.jpg")
    .resize(100, 100, ">")
    .write('resized_img_greater_than_100x100.jpg', function (err) {
        if(err) console.log(err);
        console.log("Done!")
    });
    
// Resize image if image width is less than 100 and image height is less than 100
gm("sample_image.jpg")
    .resize(100, 100, "<")
    .write('resized_img_less_than_100x100.jpg', function (err) {
        if(err) console.log(err);
        console.log("Done!")
    });

Converting Images From One Format to Another

Another common thing we'd want to do with images, is to convert an image from one format to another. This can be done in a very straight-forward way, by simply changing the file extension to one of the supported formats:

const gm = require('gm');

gm("sample_image.jpg")
    .write('sample_image.png', function(err) {
        if(err) console.log(err);
        console.log("Jpg to png!")
    });

Cropping Images

Besides resizing images, you might also want to crop images in your application. GraphicsMagick has a crop() function and its syntax is:

gm("sample_image.png").crop(width, height, [x, y[, percentage]])

Where the parameters have the following meaning:

  • width and height are the dimensions you want to crop the image to. These are either treated as pixel numbers, or percentages of the original image depending on the percentage parameter.
  • x and y are optional parameters, and they represent the coordinates of the position from where the cropping should begin. The default value is 0 for both, meaning that the cropping starts from the upper-left corner of the image.
  • The percentage parameter is also optional, and used for specifying if the values of the given width and height represent percentages of the image dimensions or pixels. The percentage option defaults to false.
const gm = require('gm');

// Crop image to 100x100 at position 20, 20
gm("sample_image.jpg")
    .crop(100, 100, 20, 20)
    .write('resized_img_crop.jpg', function (err, value) {
        if(err) console.log(err);
        console.log(value);
    });
    

// Crop image to 50% if both width and height at position 10, 10 
gm("sample_image.jpg")
    .crop(50, 50, 10, 10, true)
    .write('resized_img_crop1.jpg', function (err, value) {
        if(err) console.log(err);
        console.log(value);
    });

Again, if you check the dimensions of the new image resized_img_crop1, you can see that it is half the size of the original image and is cropped at a particular position instead of from the upper-left corner.

The image below shows an example:
cropping images with gm

We have taken a look at how to perform image manipulation using GraphicsMagick. Next, we will see how to do that in ImageMagick.

Image Processing in Node.js With ImageMagick

The ImageMagick module has been unmaintained for some time and the developers even advise using the GraphicsMagick module instead. However, for this tutorial, we'll take a brief look at some of the main functions from this module as well.

Create a folder called node-image-magick following the steps in the previous section, install the ImageMagick module via npm and create an index.js file.

In the index.js file, add the module using require():

const im = require('image-magick');

Getting Image Information

To get image information, there is an identify() method in the image-magick module as well. The first argument is the path to the image and the second is a callback function that has two arguments: err for handling errors and info for retrieving the image information:

const im = require('image-magick');

im.identify('sample_image.jpg', function (err, info) {
    if(err) console.log(err);
    console.log(info);
});

Running this code for our image prints out:

{
  format: 'JPEG',
  'mime type': 'image/jpeg',
  class: 'DirectClass',
  geometry: '213x133+0+0',
  units: 'Undefined',
  colorspace: 'sRGB',
  type: 'TrueColor',
  'base type': 'Undefined',
  endianess: 'Undefined',
  depth: 8,
  'channel depth': { red: '8-bit', green: '8-bit', blue: '8-bit' },
  ...
  width: 213,
  height: 133
}

Resizing Images

ImageMagick provides a convenience function for resizing images that takes in an object with options for resizing the image and a callback function. Resizing images using the ImageMagick module follows the following syntax:

const im = require('image-magick');

im.resize({
    srcPath: 'sample_image.jpg',
    dstPath: 'sample_image_resized.jpg',
    width:   256
  }, function(err, stdout, stderr){
       if (err) throw err;
       console.log('resized');
});

Cropping Images

ImageMagick also provides a function for cropping images with the same syntax as the resize() function. Let's take a look at a simple usage example:

const im = require('image-magick');

im.crop({
    srcPath: 'sample_image.jpg',
    dstPath: 'sample_image_cropped.jpg',
    width: 800,
    height: 600,
    quality: 1,
    gravity: "North"
  }, function(err, stdout, stderr){
       if(err) console.log(err);
});

The documentation provides a list of the options for each of the above functions.

Conclusion

In this article we explored how to work with images in Node.js using both the ImageMagick and GraphicsMagick modules. GraphicsMagick is the better option of the two, given all the advantages it has over ImageMagick.

The GraphicsMagick module has extensive documentation on more options for manipulating images.