Replace ImageMagick with HTML & CSS
Tags: imagemagick, html, css, bash
Published: 2021-09-13 06:45:12

I read an article by Zach Leatherman about [bulk og images generation](https://www.zachleat.com/web/bulk-og/). He explains how he used ImageMagick to generate a bunch of Open Graph images.

More precisely, he wrote a simple bash script with two calls to ImageMagick. The script converts this:

![Simple rocket](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/edaingnsfl8dn81ri3xv.png)

To this:

![Open Graph Image embedding the rocket](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rumnn4pa6sj0z5u7u54n.png)

Even if ImageMagick is convenient, I think it's not the best tool for this particular task. Let's see why, and what to do about it.

## Why not ImageMagick?

ImageMagick is an awesome tool. I used it a lot for [RealFavicon](https://realfavicongenerator.net/) and bulk jobs. However, I see a few issues here.

### Not just a CLI tool: a new language

Usually, we use ImageMagick from the command line. Command line? You might think `cd`, `ls` or `echo`. But ImageMagick is not of that kind. It's more like `sed` or `awk`: as complex as it is powerful.

Look at Zach's code:

```bash
# resize image
convert "$i" -resize x530 -background $bgColor \
  -gravity center -extent 1200x630 "tmp/${i##*/}"
# add watermark
composite -gravity SouthEast -geometry +20+20 \
  "tmp/watermark.png" "tmp/${i##*/}" "${outputDir}${i##*/}"
```

`convert`, `-resize`, `-gravity`, `-extent`... This is new vocabulary we have to learn.

ImageMagick has a great documentation, with a lot of examples we can start from. Yet, be ready to go on an adventure.

Don't get me wrong: I'm sure Zach is fluent with ImageMagick and was able to write this script in a few minutes. However, I can't do this. I know ImageMagick just enough to use its doc effectively. In other words, I'm good at finding the right examples and copying them wisely. 😅

### Inconvenient trial and error

When creating something visual with code, we can't expect to achieve the perfect design on the first try, whatever the technology. "Text should be a bit larger", "We won't need that much padding", and so on. So we need a convenient feedback loop.

ImageMagick is not ideal here.

Zach's script iterates over a lot of images. That's the point. But during the tuning phase, this is counterproductive. Basically, we need to hack in a way or another:

- Replace `*.png` with `rocket.png` to select only one image.
- Prepare the ImageMagick command lines from a terminal and copy/paste them to the script when we're ready.
- ...

### ImageMagick doesn't scale well

This one it a bit out of scope here because we create rather simple images. But in general, ImageMagick is not convenient for complex compositions.

Either we need to produce several intermediate images, heading for the final image at each step. Zach creates one in his demo. Or we write a single super command which does everything. I'm sure Zach's commands could be merged.

In both cases, it's hard to write and maintain.

## The HTML way

In this section, we are going to address these issues.

First, we use HTML and CSS. The reason is obvious: we already know them. No more ImageMagick multiverse.

We also use the [Resoc image templating system](https://www.npmjs.com/package/itdk). It will help us build a template, with reload-as-you-type feature for instant feedback loop.

Can't wait? You can check the [demo repo](https://github.com/Resocio/demo-project-1-million-devs-og).

### Create the template

Let's go:

```bash
mkdir 1-million-devs
cd 1-million-devs
npx itdk init resoc-template
```

The last command creates a sample image template in `resoc-template` and opens it in the browser:

![Default template](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xfk9v16qv495v41rtvtu.png)

On the right panel, we see what the template expects as parameters. Our template will mimic Zach's script:

- Path to the main image
- Path to the watermark image
- Background color

Let's edit `1-million-devs/resoc-template/resoc.manifest.json`. We can adapt the existing content to define our own manifest:

```json
{
  "partials": {
    "content": "./content.html.mustache",
    "styles": "./styles.css.mustache"
  },
  "parameters": [
    {
      "label": "Main image",
      "name": "mainImage",
      "type": "imageUrl",
      "demoValue": "rocket.png"
    },
    {
      "name": "logo",
      "type": "imageUrl",
      "demoValue": "netlify-logo.png"
    },
    {
      "name": "backgroundColor",
      "type": "color",
      "demoValue": "#00dc9e"
    }
  ]
}
```

Back to the browser, we can see that these new parameters were taken into account:

![Template parameters](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdcidttzimnlunluzxj8.png)

To get a working preview of the template, we needs a demo image and a logo. Save [the rocket](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z2dyebn4xr4q54f2zcwf.png) to `1-million-devs/resoc-template/rocket.png` and [Netlify logo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o2oenh7gm2d98yr2y6oh.png) to `1-million-devs/resoc-template/netlify-logo.png`.

The previews of the left panel are quite broken at this point. It's time to create our own template.

Edit `1-million-devs/resoc-template/content.html.mustache`:

```html
<div class="wrapper">
  <img id="main-image" src="{{ mainImage }}" />
  <img id="logo" src="{{ logo }}" />
</div>
```

The content is straightforward, expect for the strange `{{ mainImage }}` and `{{ logo }}`. This is Mustache, the templating system Resoc uses to inject the final values.

Now for the CSS, fill `1-million-devs/resoc-template/styles.css.mustache` with:

```css
.wrapper {
  background-color: {{ backgroundColor }};
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
}

#main-image {
  max-height: 84vh;
  max-width: 84vh;
}

#logo {
  position: absolute;
  right: 4vh;
  bottom: 4vh;
  height: 10vh;
}
```

Nothing fancy. Just a matter of centering the main image and put the logo to the bottom left. Background color is set by a parameter, via `{{ backgroundColor }}`. We use the `vh` unit to speak relatively to the view port height. As we plan to generate only 1200x630 images, we could have just used pixels.

Take a look at the preview in your browser:

![Final template](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f6xortwjpwaorjq29jti.png)

Cool! You could think this was made with ImageMagick 😆

### Use the template

We are now going to build the command line that will create the images. We don't need to start from scratch, though, because the template viewer already provides one below the previews:

![Image creation sample command line](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/go9zlktogj729vqnqnmp.png)

Let's shamelessly steal Zach's bash script, replace the two ImageMagick invocations with Resoc's, and name this new script `create-og-images.sh`:

```bash
#!/bin/sh

echo "Uses Resoc"

images="input/*.png"
watermark="resoc-template/netlify-logo.png"
outputDir="output/"
bgColor="#00dc9e"

mkdir -p $outputDir

for i in $images
do
  create-img resoc-template/resoc.manifest.json \
    -o "${outputDir}${i##*/}" \
    --params \
      mainImage="$i" \
      logo="$watermark" \
      backgroundColor="$bgColor" \
    -w 1200 -h 630
done
```

We need to drop [a few samples images](https://github.com/Resocio/demo-project-1-million-devs-og/tree/main/input) in a new `input` directory.

`create-img` could be called with `npx`, but this is way too slow. Install it once for all and go create!

```bash
npm install -g create-img
./create-og-images.sh
```

A few seconds later, the script turned this:

![Script input pictures](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kzg3t6wjshnn4wvyqoat.png)

To this:

![Script output pictures](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0cpwjftmkt2c4u9amkla.png)

## Conclusion

ImageMagick is an amazing tool. It can perform effects which are beyond HTML and CSS capabilities. Regarding composition, the balance is reversed. Web technologies are more suitable and better known.

Yet, HTML is somehow not available from the command line. Puppeteer has been here for a while, but it is too raw to be used in this context.

I'm creating the [Resoc image template dev kit](https://www.npmjs.com/package/itdk) to fill this gap. HTML and CSS are incredibly powerful and we know them so well. Just like JS entered the desktop world via Electron, HTML should join the command line universe.

And there are plenty of other usages for image templates, like [static social image generation for NextJS](https://dev.to/philippe/static-automated-social-images-with-nextjs-2leo).