How to Create Watermarks with Sharp in Node.js: A Step-by-Step Guide

M. H. NahibM. H. Nahib
5 min read

Have you ever wondered how websites automatically generate watermarks on uploaded images? If you're curious about how it's done, you're in the right place! In this tutorial, I will guide you on how to achieve this using Node.js and the popular image-processing library, Sharp.

Note that this project is a personal pet project of mine, built for my own use. It may include a few images related to the project.

I'm using Node.js v20.11.1 for this project, but feel free to use any version.

Project Setup

To start, I'll create a new project in a folder named watermark:

npm init -y

Next, we'll install the Sharp package with npm:

npm i sharp

Project Structure

The project contains several files that need to be created and they are assets, output, src.

📦watermark
 ┣ 📂assets
 ┃ ┣ 📜01.jpg
 ┃ ┣ 📜02.jpg
 ┃ ┗ 📜watermark.png
 ┣ 📂output
 ┃ ┣ 📜01-with-watermark.png
 ┃ ┗ 📜02-with-watermark.png
 ┣ 📂src
 ┃ ┗ 📜watermark.generator.js
 ┣ 📜.gitignore
 ┣ 📜index.js
 ┣ 📜LICENSE
 ┣ 📜package-lock.json
 ┣ 📜package.json
 ┗ 📜readme.md

assets will manage the assets that need to be watermarked and output will store the watermarked images. src is for storing all the codes related to the project.

Code Implementation

We will follow the class-based approach of JavaScript for the project. Under the src folder, we will create a file named watermark.generator.js. First of all, we will create a class named WatermarkGenerator and it will be exported.

import sharp from 'sharp';
import path from 'path';

export class WatermarkGenerator {
  async generateWatermark(image, watermark) {
    const imageMetadata = await sharp(image).metadata();
    const watermarkMetadata = await sharp(watermark).metadata();

    const newWidth = Math.floor(imageMetadata.width * 0.2);
    const newHeight = Math.floor(imageMetadata.height * 0.2);

    // Resize the watermark image to the new dimensions
    const resizedWatermark = await sharp(watermark)
      .resize(newWidth, newHeight)
      .toBuffer();

    const { left, top } = await this.#calculateWatermarkPosition(
      image,
      resizedWatermark
    );

    await sharp(image)
      .composite([
        {
          input: resizedWatermark,
          top: top,
          left: left,
        },
      ])
      .toFile(this.#pathGenerator(image));
  }

  async bulkGenerateWatermark(images, watermark) {
    const promises = images.map((image) =>
      this.generateWatermark(image, watermark)
    );
    await Promise.all(promises);
    if (promises) console.log("Watermarks generated successfully");
  }

  #pathGenerator(image) {
    return `./output/${path
      .basename(image)
      .replace(
        ".png" || ".jpg" || ".jpeg" || ".webp" || ".gif" || ".svg",
        ""
      )}-with-watermark.png`;
  }

  async imagMetadata(image) {
    return await sharp(image).metadata();
  }

  async #calculateWatermarkPosition(image, watermark) {
    const { height, width } = await this.imagMetadata(image);

    const { height: watermarkHeight, width: watermarkWidth } =
      await this.imagMetadata(watermark);

    const left = Math.floor(width - watermarkWidth - 20);
    const top = Math.floor(height - watermarkHeight - 20);

    return { left, top };
  }
}

Code Explanation

We define a class named WatermarkGenerator which will handle the watermarking process.

  1. generateWatermark Method: This method takes an image and a watermark as input. It retrieves the metadata of both the image and the watermark, resizes the watermark to 20% of the image's dimensions (you can change the value as per your need), calculates the position where the watermark should be placed, and then composites the watermark onto the image. The final watermarked image is saved to the output directory.

  2. bulkGenerateWatermark Method: This method allows for bulk processing of images. It takes an array of images and a watermark and applies the watermark to each image concurrently using Promise.all.

  3. #pathGenerator Method: This private method generates the output path for the watermarked image. It ensures the new file name is based on the original image name with a suffix indicating it has a watermark.

  4. imagMetadata Method: This method retrieves the metadata of an image using Sharp.

  5. #calculateWatermarkPosition Method: This private method calculates the position where the watermark should be placed on the image. It positions the watermark 20 pixels from the bottom-right corner of the image (you can change the value as per your need).

Now we will create a new file named index.js and we will try to upload bulk images from the assets folder and it will generate the watermarked image for us.

import path from "node:path";
import fs from "node:fs/promises";

import { WatermarkGenerator } from "./src/watermark.generator.js";

const Watermark = new WatermarkGenerator();
const readImages = async (folderPath) => {
  const files = await fs.readdir(folderPath);

  const filePaths = files
    .filter((file) => path.basename(file) !== "watermark.png")
    .map((file) => path.resolve(folderPath, file));

  return filePaths;
};

const files = await readImages("assets");

await Watermark.bulkGenerateWatermark(files, watermark);

In the index.js file, the process of generating watermarked images in bulk is as follows:

Import necessary modules such as path and fs/promises, and the WatermarkGenerator class from watermark.generator.js. Now create an instance of the WatermarkGenerator class. Define an asynchronous function readImages that reads all files from the assets folder, filters out the watermark image (watermark.png), and returns the absolute paths of the remaining images. Call readImages to get the list of image file paths from the assets folder. Use the bulkGenerateWatermark method of the WatermarkGenerator instance to apply the watermark to all the images concurrently.

I have uploaded two images to my asset folder.

Image 1

image 2

and a watermark

Now let’s run the project.

node index

If the process is successful it will show the message like Watermarks generated successfully.

Now let’s check the watermarked images. Go to the output folder and you will find all the watermarked images. For me they are,

Conclusion

In this tutorial, we've walked through the process of creating watermarks on images using Node.js and the Sharp library. By setting up a project, organizing the necessary files, and implementing a class-based approach, you can efficiently watermark images either individually or in bulk. This method ensures that your images are protected and branded, making it a valuable tool for personal projects or professional use. With the provided code and steps, you should now be able to generate watermarked images seamlessly.

You can visit the GitHub of the project as well as the original image creator.

0
Subscribe to my newsletter

Read articles from M. H. Nahib directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

M. H. Nahib
M. H. Nahib

Hi, I am Nahib. Working as a software engineer at ImpleVista.