DEV Community

Cover image for How to generate placeholder images with a simple Node script πŸ–ΌοΈπŸ–ŒοΈπŸ€–
Pascal Thormeier
Pascal Thormeier

Posted on

22 5

How to generate placeholder images with a simple Node script πŸ–ΌοΈπŸ–ŒοΈπŸ€–

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:

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

That sounds like a lot of parameters, though. Luckily, there's two packages that would help handle that many parameters: command-line-args and command-ine-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:

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

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

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

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:

/**
 * 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)
)
Enter fullscreen mode Exit fullscreen mode

Now I used the canvas package to create an image:

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))
Enter fullscreen mode Exit fullscreen mode

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

const buffer = canvas.toBuffer('image/png')
fs.writeFileSync(options.output, buffer)
Enter fullscreen mode Exit fullscreen mode

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:

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}`)
}
Enter fullscreen mode Exit fullscreen mode

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) and npm (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 β˜• or follow me on Twitter 🐦!

Buy me a coffee button

SurveyJS custom survey software

JavaScript UI Libraries for Surveys and Forms

SurveyJS lets you build a JSON-based form management system that integrates with any backend, giving you full control over your data and no user limits. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

πŸ‘‹ Kindness is contagious

Please leave a ❀️ or a friendly comment on this post if you found it helpful!

Okay