DEV Community

Cover image for Replace ImageMagick with HTML & CSS
Philippe Bernard
Philippe Bernard

Posted on • Originally published at blog.philippebernard.dev

Replace ImageMagick with HTML & CSS

I read an article by Zach Leatherman about bulk og images generation. 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

To this:

Open Graph Image embedding the rocket

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

# 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##*/}"
Enter fullscreen mode Exit fullscreen mode

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

Create the template

Let's go:

mkdir 1-million-devs
cd 1-million-devs
npx itdk init resoc-template
Enter fullscreen mode Exit fullscreen mode

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

Default template

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:

{
  "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"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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

Template parameters

To get a working preview of the template, we needs a demo image and a logo. Save the rocket to 1-million-devs/resoc-template/rocket.png and Netlify logo 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:

<div class="wrapper">
  <img id="main-image" src="{{ mainImage }}" />
  <img id="logo" src="{{ logo }}" />
</div>
Enter fullscreen mode Exit fullscreen mode

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:

.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;
}
Enter fullscreen mode Exit fullscreen mode

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

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

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:

#!/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
Enter fullscreen mode Exit fullscreen mode

We need to drop a few samples images 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!

npm install -g create-img
./create-og-images.sh
Enter fullscreen mode Exit fullscreen mode

A few seconds later, the script turned this:

Script input pictures

To this:

Script output pictures

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

Discussion (0)