Converting image to CSS box-shadow property.
Tags: css, boxshadow, cli, node
Published: 2023-07-29 09:29:21

## 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](https://github.com/jimp-dev/jimp)

**What is Jimp?**
[Jimp](https://github.com/jimp-dev/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.

```js
const image = await Jimp.read('./path/to/an/image.jpg');
```

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

```json
Jimp {
  ...,
  bitmap: {
    width: 275,
    height: 344,
    data: <Buffer f8 ff f3 ff ff fe ... 378350 more bytes>
  },
  ...,
}
```
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.

```js
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.
    }
```

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:

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

```

- **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: 

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

```
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. 

```js
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];
    }
```
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.

```js
    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})`]);
    }
```

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

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

The final code will look like this:

```js
    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')};
      }`
    );
```

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

Hooray :tada:, 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](https://www.npmjs.com/package/@tonalmathew/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 :zap:.