Pascal Akunne A JavaScript developer focused on building human-centric products with HTML, CSS, React, Node.js, and MongoDB

Processing images with sharp in Node.js

8 min read 2493

Processing images with sharp in Node.js

Images are an important component of most applications that handle user-generated content. However, excessively large or unoptimized image files can negatively impact performance and user experience. A robust image processing solution can be invaluable for UGC management.

sharp is a high-performance image processing module for Node.js. This module assists with UGC management by offering an easy solution for reading, enhancing, and saving image files. sharp compresses images faster than most other Node.js modules, like ImageMagick, Jimp, or Squoosh, and produces high-quality results.

sharp converts large common image formats to smaller, web-friendly images. sharp can read JPEG, PNG, WebP, AVIF, TIFF, GIF, and SVG image formats. This module can produce images in JPEG, PNG, WebP, AVIF, and TIFF formats as well as uncompressed raw pixel data.

In this tutorial, we’ll analyze and modify an image using the sharp Node.js library. The only prerequisite for this tutorial is a system set up with Node.js and npm.

We’ll cover the following:

Setting up a sharp image project

To set up a sharp image project, start by creating a new directory in the editor’s terminal:

mkdir sharp_project

Next, migrate to a new directory:

cd sharp_project

Now, initialize npm:

npm init -y

Next, install sharp:

npm install sharp

Now, open the directory using your favorite IDE. In this tutorial, we’ll use VS Code.

code .

We’ll use the following two images:

Sharp Image Project Sample Image

Sharp Image Project Sample Background Image

Converting an image to grayscale

To convert an image to grayscale, create a new file in the editor’s terminal:

touch _grayscale.js

Next, copy the following code:

const sharp = require('sharp')

const convertTograyscale = () => {
  sharp('./images/robo.jpg')
  .grayscale() // or .greyscale()
  .toFile(__dirname + '/processed_images/grayscale_robo.jpg')
}

convertTograyscale()

In this example, we start by creating an instance of the sharp module. We use the sharp() instance within the require() function scope to read the path to our image.

To carry out the processing task, we define the convertTograyscale() function. Then, we chain the sharp module’s grayscale() method to the sharp instance to alter the image’s appearance. We save the altered image in the processed_images folder using the toFile() method.

Now, we run the file code on the terminal:

node _grayscale.js

The newly created grayscale image, grayscale_robo.jpg, can be found in the processed_image folder:

Image Converted To Grayscale With Sharp

Tinting an image

To tint an image, start by creating a new file:

touch _tint.js

Now, copy the following code:

const sharp = require('sharp')

const tintImage = () => {
  sharp ('./images/robo.jpg')
  .tint({r: 255, g: 0, b: 0})
  .toFile(__dirname + '/processed_images/tint_robo.jpg')
}

tintImage()

We use the tint() method of the sharp module to alter the color of the image. With this method, we can specify the intensity of the red, green, and blue chroma values. The scale for this method is 0 to 255, with higher chroma values resulting in a more saturated tint. For this example, we use the 255 maximum value for the red parameter: r.

Save the file and run the following command:

node _tint.js

Here’s the newly created tinted image, tint_robo.jpg:

Image Tinted With Sharp

Extracting image metadata

To extract the image metadata, start by creating a new file:

touch _metadata.js

Next, copy the following code:

const sharp = require('sharp')

const imageMetadata = () => {
  const metadata = sharp('./images/robo.jpg').metadata()

  console.log(metadata)
}

imageMetadata()

We use the metadata() function to extract the image metadata. We save the metadata variable and log to the terminal using console.log().

Now, we run the following command:

node _metadata.js

This produces the following output:

{
  format: 'jpeg',
  width: 1920,
  height: 1080,
  space: 'srgb',
  channels: 3,
  depth: 'uchar',
  density: 72,
  chromaSubsampling: '4:2:0',
  isProgressive: false,
  hasProfile: false,
  hasAlpha: false,
  orientation: 1,
  exif: <Buffer 45 78 69 66 00 00 4d 4d 00 2a 00 00 00 08 00 05 01 12 00 03 00 00 00 01 00 01 00 00 01 1a 00 05 00 00 00 01 00 00 00 4a 01 1b 00 05 00 00 00 01 00 00 ... 88 more bytes>,
  iptc: <Buffer 50 68 6f 74 6f 73 68 6f 70 20 33 2e 30 00 38 42 49 4d 04 04 00 00 00 00 00 00 38 42 49 4d 04 25 00 00 00 00 00 10 d4 1d 8c d9 8f 00 b2 04 e9 80 09 98 ... 4 more bytes>
}

Rotating an image

To rotate an image, start by creating a new file:

touch _rotate.js

Now, add the following code:

const sharp = require('sharp');

const rotateImage = () => {
  sharp('./images/robo.jpg')
  .rotate(250)
  .toFile(__dirname + '/processed_images/rotate_robo.jpg')
}

rotateImage()

In this example, the rotateImage() function reads the image and returns it rotated by 250deg. We use the rotate() method within the function scope, which is chained to the sharp module. The rotate() method takes the rotation angle as an input and saves it as a new image: rotate_robo.jpg.

Now, run the file code:

node _rotate.js

Here’s the rotated image:

Image Rotated With Sharp

Resizing an image

To resize an image, first create a new file:

touch _resize.js

Next, copy the following code:

const sharp = require('sharp')

const resizeImage = () => {
  const resize = sharp('./images/robo.jpg')
  .resize(350, 260)
  .toFile(__dirname + '/processed_images/resize_robo.jpg')

  console.log(resize)
}

resizeImage()

To resize the image, we first chain the resize() function to the sharp instance. Then, we save it in the processed_images folder. This procedure alters the overall dimensions without cropping or distorting the image.

Now, we run the following command:

node _resize.js

This produces the following output:

{
  format: 'jpeg',
  width: 350,
  height: 260,
  channels: 3,
  premultiplied: false,
  size: 12042
}

Formatting an image

const formatImage = () => {
  sharp('./images/robo.jpg')
  .toFormat('png', {palette: true})
  .toFile(__dirname + '/processed_images/format_robo.png')
}

formatImage()

To change the sharp instance’s file format from JPEG to PNG, we use the format() method. We also give the format() method an option to compress the image.



Each formatImage() accepts a separate object with various properties. The palette property, for example, is only valid on PNG images. Only JPEG pictures can use the mozjpeg property, and only WebP images can use the lossless property. The toFile() method is used to save the compressed image in the processed_images folder.

Cropping an image

To crop an image, start by creating a new file:

touch _crop.js

Next, copy the following code:

const sharp = require('sharp')

const cropImage = () => {
  sharp('./images/robo.jpg')
  .extract({left: 740, width: 500, height: 300, top: 340})
  .toFile(__dirname + '/processed_images/crop_robo.png')
}

cropImage()

To crop the image, we first chain the extract() function to the sharp instance. Then, we save it in the processed_images folder. With this procedure, we can specify: the horizontal space to be cropped to the left of the image (left), the desired image width, the desired image height, and the vertical space to be cropped above the image (top).

In this example, we generate a cropping box 500px wide by 300px high and positioned 740px from the image’s left border and 340px from the image’s top border. With the extract method, any part of the image that fits inside the box will be retained. Anything outside the box will be removed.

Now, copy the following code:

node _crop.js

Here’s the copped image:

Image Cropped With Sharp

Creating a composite image

To combine two or more images, first create a new file:

touch _composite.js

Next, copy the following code:

const sharp = require('sharp')

const compositeImage = () => {
  sharp ('./images/fall.jpg')
  .composite([{input: './images/robo.jpg', left: 900, top: 750}])
  .toFile(__dirname + '/processed_images/composite_robo.jpg')
}

compositeImage()

To create a composite image, we first chain the composite() function to the sharp instance. We also add a new image file, fall.jpg, to the sharp module. In this example, fall.jpg is the processed image (or background image).

The sharp module’s composite() method takes an array as an input. The image to be composited, robo.jpg, is read by a single object in the array. It is important to note that the composite image must have smaller dimensions than the processed image. We can specify the following object attributes:

  • input: the image that will be placed over the processed image
  • top: the vertical position of the image composited over the processed image
  • left: the horizontal position of the image composited over the processed image

Save the resulting image, and run the following command:

node _composite.js

Here’s the composite image:

Composite Image Created With Sharp

Blurring an image

To blur an image, start by creating a new file:

touch _blur.js

Next, add the following code:

const sharp = require('sharp')

const blurImage = () => {
  sharp('./images/robo.jpg')
  .blur(9)
  .toFile(__dirname + '/processed_images/blur_robo.jpg')
}

blurImage()

In our example, we use the blur() method of the sharp module to apply a Gaussian blur to the image. This technique uses the Gaussian function to give the pixels at the edge of the image less weight, resulting in reduced image detail and image noise. For this example, the Gaussian .blur() sigma is 9.

Save the file, and run the following command:

node _blur.js

Here’s the blurred image:

Image Blurred With Sharp

Sharpening an image

To sharpen an image, start by creating a new file:

touch _sharpen.js

Next, add the following code:

const sharp = require('sharp')

const sharpenImage = () => {
  sharp ('./images/robo.jpg')
  .sharpen(13)
  .toFile(__dirname + '/processed_images/sharpen_robo.jpg')
}

sharpenImage()

To sharpen the image, we use the sharpen() function. If no parameters are specified, this technique will produce a quick, moderately sharpened version of the original image. However, if a parameter is specified, this function will perform a slower, more precise sharpening. For this example, the sharpen() sigma is 13.

Now, copy the following code:

node _sharpen.js

Here’s the sharpened image:

Image Sharpened With Sharp

Flipping an image

To flip (or horizontally reverse) an image, start by creating a new file:

touch _flip.js

Next, copy the following code:

const sharp = require('sharp')
const flipImage = async () => {
  await sharp('./images/robo.jpg')
  .flip()
  .toFile(__dirname + '/processed_images/flip_robo.jpg');
}
flipImage()

We chain the flip() method to the sharp instance to flip the image over the x-axis.

Now, run the following program:

node _flip.js

Here’s the flipped image:

Image Flipped With Sharp

Flopping an image

To flop (or vertically reverse) an image, start by creating a new file:

touch _flop.js

Next, add the following code:

const sharp = require('sharp')
const flopImage = async () => {
  await sharp('./images/robo.jpg')
  .flop()
  .toFile(__dirname + '/processed_images/flop_robo.jpg');
}
flopImage()

We chain the flop() method to the sharp instance to flop the image over the y-axis.

Now, run the following command:

node _flop.js

Here’s the flopped image:

Image Flopped With Sharp

Adding text to an image

sharp does not currently have a method for adding text to an image. However, as a workaround, we can draw text using SVG and then use the composite() method to add the text image to the original image.

Start by creating a new file:

touch _text.js

Next, copy the following code:

const sharp = require('sharp');

const addText = () => {
  const width = 900;
  const height = 500;
  const text = "E.T, go home";

  const svgText = `
  <svg width="${width}" height="${height}">
    <style>
      .title { fill: red; font-size: 85px}
    </style>
    <text x="45%" y="40%" text-anchor="middle" class="title">${text}</text>
  </svg>`

  const svgBuffer = Buffer.from(svgText);

  sharp ('./images/robo.jpg')
  .composite([{input: svgBuffer, left: 1150, top: 90}])
  .toFile(__dirname + '/processed_images/text_robo.jpg')
}

addText()

For our workaround, we use an addText() function with four variables: the desired text area width, the desired text area height, the text string that will be drawn using SVG (in this example, E.T, go home), and a transparent container, svgText, that will hold the text.

The SVG element has two child elements: style and text. The style element uses CSS to alter the appearance of the text. The fill and font-size properties modify the color and the size of the text, respectively.

The text element has four attributes: horizontal position (x), vertical position (y), alignment (text-anchor), and class. This last attribute, class, specifies how the CSS styles should be applied to the text element. ${text} interpolates the string E.T, go home from the variable text.

Next, we use the Buffer.from() function to create a Buffer object from the svgText variable and then store it in the svgBuffer variable.

To add the text to the image, we chain the composite() function to the sharp instance and use the svgText variable as the input. In this example, robo.jpg is the processed image (or background image).

Now, run the following command:

node _text.js

Here’s the image with text:

Image With Text Added With Sharp

Storing the image in a database

You may wish to store your processed image(s) in a database while working with the sharp library. This operation may be carried out in two ways:

  1. Using JavaScript Promises
  2. Using the async/await syntax

Before we launch into the two ways to store the image, you’ll want to create a new file named touch_database.js.

Using JavaScript Promises

A Promise is an object that represents the success or failure of an asynchronous operation and its generated value. sharp returns a Promise that can be used to perform actions whenever we’re done processing the image.

const sharp = require('sharp')

const usingPromise = () => {
  sharp('./images/robo.jpg')
  .extract({left: 740, width: 500, height: 300, top: 340})
  .then(() => {


    // Write code to store the image to the database


  })
  .catch((err) => console.warn(err));
}

usingPromise()

Promises provide two techniques for dealing with its result. A successful result is handled by the .then() method. In this example, after the image is successfully processed, the .then() method executes the code to save the image to the database. If the image process fails, the failure result is handled by .catch().

Using the async/await syntax

The async keyword simplifies working with asynchronous, Promise-based code, which makes the code appear and behave like synchronous code.

const usingAsync = async() => {
  try{
    const image = await sharp('./images/robo.jpg')
    .extract({left: 740, width: 500, height: 300, top: 340})
    .toFile(__dirname + '/processed_images/crop_robo.png')

    // Write code to store image to the database

    return image

  }catch(e){
     // handles error if any
  }
}
usingAsync()

Inside the async function the trycatch block is used to handle errors. The await keyword is used before a call to a function that returns a Promise.

Conclusion

In this tutorial, we reviewed how to use the sharp library to process images in Node.js. We used the grayscale(), tint(), rotate(), resize(), crop(), blur(), sharpen(), flip(), and flop() methods to alter the image’s appearance, style, and shape. We extracted the image metadata using the metadata() method. We combined two images using the composite() method. We also used the composite()method to add text to the image with an SVG workaround. Lastly, we used the format() method to change the image type and compress the image.

To improve the efficiency and performance of your Node.js application even further, here are some additional recommendations.

200’s only Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
Pascal Akunne A JavaScript developer focused on building human-centric products with HTML, CSS, React, Node.js, and MongoDB

Leave a Reply