How to generate placeholder images with a simple Node script 🖼️🖌️🤖
Tags: node, javascript, tutorial, showdev
Published: 2021-01-16 19:14:58

I needed placeholder images. Like, _a lot_. With some custom text on them and different colors. After searching the web for a bit, I found no service that did exactly what I wanted to do, so I decided to just write a little node script myself! 😀

# What it should do 🤔

I wanted to have a node script I could call via CLI that would generate a single PNG with some given parameters. I should be able to change its color, size, text, maybe font and I should be able to define where the image will end up. So I came up with some CLI parameters:

```bash
--width (-w)  # Width of the image
--height (-h) # Height of the image
--red (-r)    # Red, 0-255
--green (-g)  # Green, 0-255
--blue (-b)   # Blue, 0-255
--text (-t)   # Text, defaults to "Lorem ipsum"
--font (-f)   # The font of the text, defaults to "sans-serif"
--output (-o) # Where the image would end up, defaults to "./image.png"
```

That sounds like a lot of parameters, though. Luckily, there's two packages that would help handle that many parameters: [command-line-args](https://www.npmjs.com/package/command-line-args) and [command-ine-usage](https://www.npmjs.com/package/command-line-usage). Those were exactly what I needed. Off to the implementation!

# Implementing the CLI stuff ⌨️

That was rather straight forward. I read the docs a bit and came up with this:

```javascript
// generate.js

#!/usr/bin/node

const commandLineArgs = require('command-line-args')
const commandLineUsage = require('command-line-usage')
const version = require('./package.json').version

const optionDefinitions = [
  { name: 'width', alias: 'w', type: Number, defaultValue: 640, description: 'Width of the image. Default: 640' },
  { name: 'height', alias: 'h', type: Number, defaultValue: 480, description: 'Height of the image. Default: 480' },
  { name: 'red', alias: 'r', type: Number, defaultValue: 255, description: 'Red part, 0-255. Default: 255' },
  { name: 'green', alias: 'g', type: Number, defaultValue: 255, description: 'Green part, 0-255. Default: 255' },
  { name: 'blue', alias: 'b', type: Number, defaultValue: 255, description: 'Blue part, 0-255. Default: 255' },
  { name: 'text', alias: 't', type: String, defaultValue: 'Lorem ipsum', description: 'Text to put on image. Default: "Lorem ipsum"' },
  { name: 'font', alias: 'f', type: String, defaultValue: 'sans-serif', description: 'Font the text will be rendered in. Default: "sans-serif"' },
  { name: 'output', alias: 'o', type: String, defaultValue: './image.png', description: 'Path of the image. Default: "./image.png"' },
  { name: 'help', type: Boolean, defaultValue: false, description: 'Prints this help' },
  { name: 'version', alias: 'v', type: Boolean, defaultValue: false, description: 'Prints the version' },
]
const options = commandLineArgs(optionDefinitions)

if (options.version) {
  console.log(version)
  return
}

if (options.help) {
  const sections = [
    {
      header: 'Placeholder image generator',
      content: 'Create placeholder images with a single line of bash!'
    },
    {
      header: 'Arguments',
      optionList: optionDefinitions
    },
    {
      header: 'Example',
      content: './generate.js -w 100 -h 100 -r 0 -g 0 -b 255 -t "Hello, World!" -f Helvetica -o ./placeholder.png'
    }
  ]

  const usage = commandLineUsage(sections)
  console.log(usage)

  return
}
```

Executing `./generate.js --help` would print this now:

```bash
./generate.js --help

Placeholder image generator

  Create placeholder images with a single line of bash! 

Arguments

  -w, --width number    Width of the image. Default: 640                         
  -h, --height number   Height of the image. Default: 480                        
  -r, --red number      Red part, 0-255. Default: 255                            
  -g, --green number    Green part, 0-255. Default: 255                          
  -b, --blue number     Blue part, 0-255. Default: 255                           
  -t, --text string     Text to put on image. Default: "Lorem ipsum"             
  -f, --font string     Font the text will be rendered in. Default: "sans-serif" 
  -o, --output string   Path of the image. Default: "./image.png"                
  --help                Prints this help                                         
  -v, --version         Prints the version                                       

Example

  ./generate.js -w 100 -h 100 -r 0 -g 0 -b 255 -t "Hello, World!" -f Helvetica  
  -o ./placeholder.png    
```

Amazing, that was exactly what I wanted!

# Actually generating the image 🎨

With all these parameters, I would be able to actually generate a placeholder image. Since the text should be readable on whatever color the user specifies, the color of the text would need to be "the opposite" of the background color. Also, I needed the colors as hex strings, not RGB. So I created these two functions:

```javascript
/**
 * Transforms R, G and B into a hex color string.
 * @param r
 * @param g
 * @param b
 * @returns {string}
 */
const colorToHex = (r, g, b) => '#' +
  (r.toString(16).padStart(2, '0')) +
  (g.toString(16).padStart(2, '0')) +
  (b.toString(16).padStart(2, '0'))

/**
 * Inverts a color and returns its hex value
 * @param r
 * @param g
 * @param b
 * @returns {string}
 */
const invertColor = (r, g, b) => colorToHex(
  (255 - r),
  (255 - g),
  (255 - b)
)
```

Now I used the [canvas](https://www.npmjs.com/package/canvas) package to create an image:

```javascript
const width = options.width
const height = options.height
const color = colorToHex(options.red, options.green, options.blue)
const textColor = invertColor(options.red, options.green, options.blue)

const canvas = createCanvas(width, height)
const context = canvas.getContext('2d')

context.fillStyle = color
context.fillRect(0, 0, width, height)
context.fillStyle = textColor
// height / 10 scales the font so it always looks nice!
context.font = `${height / 10}px ${options.font}`

const textSize = context.measureText(options.text)

context.fillText(options.text , (canvas.width / 2) - (textSize.width / 2), (canvas.height / 2))
```

... and used `fs` to write the image to the harddisk:

```javascript
const buffer = canvas.toBuffer('image/png')
fs.writeFileSync(options.output, buffer)
```

Awesome! A little test showed that the images were generated correctly.

# Adding some convenience 🛋️

Almost done. Because I had an idea: why not also make the script open the image in the user's default application? `process.platform` and Node's `exec` allowed me to do just this:

```javascript
if (options.open) {
  let command = 'xdg-open' // Linux
  if (process.platform === 'win32') { // Windows
    command = 'start'
  }

  if (process.platform === 'darwin') { // OSX
    command = 'open'
  }

  exec(`${command} ${options.output}`)
}
```

And that's it: A CLI tool to create an image of a configured size with configured color and a text that scales automatically!

# Wait, why the `#showdev` tag? 😯

Because I made it open source! 🚀 You can find it on [GitHub (`thormeier/generate-placeholder-image`)](https://github.com/thormeier/generate-placeholder-image) and [npm (`generate-placeholder-image`)](https://www.npmjs.com/package/generate-placeholder-image)

---

*I hope you enjoyed reading this article as much as I enjoyed writing it! If so, leave a* ❤️ *or a* 🦄*! I write tech articles in my free time and like to drink coffee every once in a while.*

*If you want to support my efforts, please consider* [*buying me a coffee* ☕](http://buymeacoffee.com/pthormeier) *or* [*follow me on Twitter* 🐦](https://twitter.com/pthormeier)*!*

[![Buy me a coffee button](https://dev-to-uploads.s3.amazonaws.com/i/x55hp6jopwyy161d8e2u.png)](http://buymeacoffee.com/pthormeier)