Create OG Images Without An Image

I make a bunch of little sites. That means coming up with a bunch of OG images for social media cards. That's not my strong suite. So, I've decided to punt.

My new approach is to generate images with a solid color background and some text. No more trying to find a photo that goes with the page. That way, I can get on with deploying the site and come back to add a proper image later if the spirit moves me.

I use Cloudinary to create the images automatically. Their service works by assembling URLs with a bunch of parameters they use to generate the image. It's a bit finicky, so I put this script together to take care of it for me. You can see the output below:


// CONFIG

const backgroundColor = '000000'
const font_color = 'dddddd'
const base_image = 'base.png'

const texts = [
    {
        text: 'The small pup gnawed a hole in the sock',
        font: 'Abhaya Libre_110_bold',
        box_width: 700,
        box_height: 300,
        horizontal_offset: 50,
        vertical_offset: 50,
        gravity: 'north_west',
    },
    {
        text: 'from alan w. smith',
        font: 'Inconsolata_30_bold',
        box_width: null,
        box_height: null,
        horizontal_offset: 40,
        vertical_offset: 40,
        gravity: 'south_west',
    },
    {
        text: 'alanwsmith.com',
        font: 'Inconsolata_30_bold',
        box_width: null,
        box_height: null,
        horizontal_offset: 40,
        vertical_offset: 40,
        gravity: 'south_east',
    },
]
 
// EXECUTION

const encode = (raw) => {
    return encodeURI(raw).replaceAll(/,/g, '%252C')
}

const parts = texts.map((text) => {
    let width = ""
    if (text.box_width) {
        width = `,w_${text.box_width}`
    }
    let height = ""
    if (text.box_height) {
        height = `,h_${text.box_height}`
    }
    const preface = `c_fit${width}${height}`
    const color = `co_rgb:${font_color}`
    const content = `l_text:${encode(text.font)}:${encode(text.text)}`
    const gravity = `fl_layer_apply,g_${text.gravity}`
    const position = `x_${text.horizontal_offset},y_${text.vertical_offset}`
    const part = `${preface},${color},${content}/${gravity},${position}`
    return part 
})

const front = `https://res.cloudinary.com/demo/image/upload`
const global = `w_1200,h_630,b_rgb:${backgroundColor},o_0`

const url = `${front}/${global}/${parts.join('/')}/${base_image}`
console.log(url)

You can see why I want a script.

This is the image that gets generated by that url:

Totally works for me.

I usually go back and add proper images later. Until then, this gets me out the door without have to worry about making them at the start.

Notes

  • This isn't much different from how other folks use tools like Cloudinary for OG images. I'm just doing it without having to worry about what the source image looks like

  • There does need to be a base image for the process to work. A 1x1 pixel image works just fine since the final dimensions are set in the URL and the background color paints over it

  • The box_width and box_height set the bounding box the text will be set inside

  • If the length of the text is longer than the bound box can support it will be truncated with the chopped off words replaced by an ellipsis like it is in the example

  • The gravity fields determine which corner of the image and the bounding box will be used as the anchor. For example, the alanwsmith.com string in the example is set to south_east which sets the anchor to the lower right. The horizontal_offset and vertical_offset then move the lower left corner of the text up and left, respectively.

  • If the horizontal_offset and/or vertical_offset are null, the text position sticks to wherever the gravity field puts it

  • I use .png instead of .jpg because the file sizes are smaller when it's just text and a background like these

  • The o_0 sets the opacity for the color. Without it, the background color doesn't apply. It also makes a great emoji

  • Font names with spaces (e.g. Averia Sans Libre_80_bold) get translated with the same URL encoding as the text (e.g. Averia%20Sans%20Libre_40_bold)

  • Standard URI encoding takes care of everything in the text with the exception of commas. Since Cloudinary uses commas as part of their instructions they have to be handled differently. The .replaceAll(/,/g, '%252C') call does the double encoding that's necessary to get things to work

  • I put together a list of Google Fonts available in Cloudinary. Note that it doesn't include default fonts like Helvetica. I haven't looked for a list of those

  • I haven't found a way to adjust the line height, leading, etc... of the text. Creating code that assembles the URL dynamically is one approach to that. For example, instead of making a single block of text make determination of where to split lines and place them individually. Another one that would be interesting is Cloudinary's custom functions to create a WASM (or whatever) function that adjusts the placement in a similar way

  • Another nice feature would be to automatically set the size of the main font based on how many characters there are. That's left as an exercise for the reader

References