DEV Community

Tonal Mathew
Tonal Mathew

Posted on

Converting image to CSS box-shadow property.

How to convert image to a CSS box-shadow property?

Before we deep dive into that let's take a look at Images.

What is an image?
In simple context we all know that images are composed of pixels and colors.

What is a pixel?
Pixel is a tiny square or dot that contains a single color. When millions of pixels are arranged in a grid, they collectively create an image.

Now we know about images, Now lets take a look at how we can extract the pixel values and corresponding colors from an image.
For extracting image data we will be using Jimp

What is Jimp?
Jimp is a JavaScript image processing library that enables us to work with images in various ways, such as resizing, cropping, applying filters, and reading pixel data. It is particularly useful for handling image manipulation tasks in a Node.js environment.

How do we extract image data using Jimp
Jimp has an asynchronous method read(), which will take an image as parameter and it will return some data as promise.

const image = await Jimp.read('./path/to/an/image.jpg');
Enter fullscreen mode Exit fullscreen mode

Now lets take a look at image data that we are getting.

Jimp {
  ...,
  bitmap: {
    width: 275,
    height: 344,
    data: <Buffer f8 ff f3 ff ff fe ... 378350 more bytes>
  },
  ...,
}
Enter fullscreen mode Exit fullscreen mode

With that Jimp.read() function we will get a lot of data, but for our purpose we only nee the bitmap data, which contains properties like width, height and data. This data field contains the r, g, b, a values of the image that we pass. you can loop through the data and extract the r, g, b, a value of each pixel. For that we can use this code.

const { bitmap: { data } } = image;

    for (let i = 0; i < data.length; i += 4) {
      const r = data[i];
      const g = data[i + 1];
      const b = data[i + 2];
      // we don't need `a` value so we skip that.
    }
Enter fullscreen mode Exit fullscreen mode

Now how can we convert it into box-shadow property, before that let's take a look at the CSS box-shadow property.

box-shadow
The syntax of box-shadow property is:

box-shadow: [horizontal offset] [vertical offset] [blur radius] [spread radius] [color];

Enter fullscreen mode Exit fullscreen mode
  • Horizontal offset: The horizontal distance the shadow should be offset from the element.
  • Vertical offset: The vertical distance the shadow should be offset from the element.
  • Blur radius: The amount of blurring applied to the shadow.
  • Spread radius: The amount the shadow should be spread out or contracted.
  • Color: The color of the shadow.

That's not all about it, we could add multiple box shadows to an element.
The syntax for adding multiple box shadow will look like this:

box-shadow: [horizontal offset] [vertical offset] [blur radius] [spread radius] [color], 
            [horizontal offset] [vertical offset] [blur radius] [spread radius] [color], 
            ...;

Enter fullscreen mode Exit fullscreen mode

Ok now let's go back to our r, g, b value that we extracted from the image data. we can convert the r, g, b to color using the rgba(r, g, b).

Is that it? No, we need the horizontal and vertical values. We don't need the blur radius, so it will be 0px, but we need the spread radius.
Spread radius doesn't depend on the image data, so we will give it a solid value of 1px. If you gave the spread radius a value of less than 1px the image won't get displayed. If it is greater than 1px the image won't look good, so we stick to 1px.

Now that's settled, so how can we find the horizontal and vertical value?
we could use the same loop that we used to extract the r, g, b values. To get the horizontal and vertical values we could use this code.

const { bitmap: { width, data } } = image;

    for (let i = 0; i < data.length; i += 4) {
      const x = (i / 4) % width; // horizontal
      const y = Math.floor(i / (width * 4)); // vertical
      const r = data[i];
      const g = data[i + 1];
      const b = data[i + 2];
    }
Enter fullscreen mode Exit fullscreen mode

The x and y values that we get will be the (x, y) coordinates of each pixel and the r, g, b will be the corresponding color value to that pixel.

Now we get all the values that we need, now we only need to transform these data to box shadow values. For that we could use an array to push the data to it.

    const boxShadowArr = [];
    const { bitmap: { width, data } } = image;

    for (let i = 0; i < data.length; i += 4) {
      const x = (i / 4) % width;
      const y = Math.floor(i / (width * 4));
      const r = data[i];
      const g = data[i + 1];
      const b = data[i + 2];

      boxShadowArr.push([`${x}px ${y}px 0px 1px rgba(${r},${g},${b})`]);
    }
Enter fullscreen mode Exit fullscreen mode

And that's it, now we can write the boxShadowArr to a CSS file as a box shadow property.

fs.writeFile(
    "./style.css",
    `.image-to-css {
        box-shadow: ${boxShadowArr.join(',\n')};
      }`
    );
Enter fullscreen mode Exit fullscreen mode

The final code will look like this:

    const image = await Jimp.read(imagePath);

    const boxShadowArr = [];
    const { bitmap: { width, data } } = image;

    for (let i = 0; i < data.length; i += 4) {
      const x = (i / 4) % width;
      const y = Math.floor(i / (width * 4));
      const r = data[i];
      const g = data[i + 1];
      const b = data[i + 2];

      boxShadowArr.push([`${x}px ${y}px 0px 1px rgba(${r},${g},${b})`]);
    }

await fs.writeFile(
    "./style.css",
    `.image-to-css {
        box-shadow: ${boxShadowArr.join(',\n')};
      }`
    );
Enter fullscreen mode Exit fullscreen mode

If you want to use await you should keep it in an async function.

Hooray 🎉, you have successfully converted an image file to CSS box shadow property.

For you to try this I have created a CLI tool called image-to-css.
You can install it using npm and use it. The tool will take in an image file path as an argument and create a new CSS file as output.

And that's it, thanks for reading âš¡.

Top comments (0)