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"
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 }
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
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) )
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))
... and used fs
to write the image to the harddisk:
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:
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
) 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 π¦!
Top comments (3)
It is better to use service like placehold.net
You're right! However, it's a nice problem to solve and showcases image generation wiith JS, how to create a command line tool and how to approach designing such a thing. If people don't want to use an external online sercive, they can still implement their own as shown in the post. :)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.