Automatically generate custom opengraph images for each page! (PHP)

OpenGraph, or social media embeds are how your site appears when someone links to it. You normally have an image you want to show as part of this, but wouldn’t it be awesome to have a unique image with the tile of the page on every page on your site, like below?

If you are running your site on your own hosting, and can run PHP I have created a script to do this for you - completely customisable!

Follow the steps below, or watch the video.

  1. First copy the code from here
  2. Save the code into a file and call it gen.php
  3. Once downloaded, there are a couple settings you need to set.
  4. First you shall need to create a background image, and save it somewhere.

the image should be 1200 x 630 pixels in size. Its ok if your image is a little bigger or smaller, the script will make sure it is the correct size. Leave space in the center of the image for the page title to go! The image must be a PNG.

  1. You can now choose the font you want the text to be rendered in, first choose a font from Google Fonts, then download it locally. Then use this website to convert it to TTF format.

Your font must be a TTF format.

  1. Now you have your background image, and font you can now setup the script.
  2. In your websites main folder, where the webpages are saved upload the script into its own folder, name this how you want.
  3. Inside this same folder, upload your background and font files.
  4. Open the Gen.php file and under the variables at the top of the page, modify these with the locations of you files.

For $imageURL, use the full path to the background image, for example https://example.com/opengraph/background.png. For $font you should be able to just specify ./YourFontName.ttf but if this does not work, use the exact path to the font on your server. Contact your host if you do not know the exact path.

  1. Next make another folder inside where the script is saved, call this generated.

The generated folder is used to cache already generated images, if an image has already been generated the script will serve these automatically instead of making a new image. Images in this folder older than 1 minute are deleted automatically.

  1. The script is now setup, to use it in your HTML pages simply open a file and in the meta data of og:image and twitter:image link to the location of the gen.php script, appending to the end of the link ?title= after the = insert the title you want to show.

EXAMPLE
<meta name="twitter:image" content="https://example.com/opengraph/gen.php?title=Webpage%20Title">
<meta property="og:image" content="https://example.com/opengraph/gen.php?title=Webpage520Title">
Remember, you must use URL encoding, spaces become %20 use this site to convert text to URL encoding.

  1. You have now setup your site to generate custom social media embed images! Hopefully this is useful and saves you time.

Any questions, or issues please comment below. Remember that you must have hosting that lets you run PHP V7+ - this is not possible on the Bootstrap Studio Hosting.

3 Likes

I see people are clicking the links :eyes:

I’d love if people could share their projects they use this on, would absolutely make my day. Spent three days writing this script! :heart:

1 Like

Look very great, thank a lot !

1 Like

Very useful script. I made a few adjustments to suit my needs.

You can use an underscore instead of %20 in the meta. Also adding ** splits the title into a title and a subtitle.

<?php

/**
 * This script inserts text over an image using ImageMagick library.
 * The image URL, font, and title are fetched from variables.
 * The text is drawn over the center of the image in white color.
 *
 * Script written by Catkin, BlackFox IT (https://blackfox.it/)
 * additional edits by Richard @ sinfield.co 
 *
 */

// Background image location
$imageUrl = "Your Image URL";
// Path to the custom font, must be in ttf format
$font = "Your Font File";
// Get the text from the URL link.com/?title=text
$title = $_GET['title'];
// Set the subTitle to be empty
$subTitle = "";
/**
 * Inserts text over an image using ImageMagick library.
 *
 * @param string $imageUrl The URL of the image
 * @param string $font The font to be used
 * @param string $title The text to be inserted
 * @return string The path of the modified image
 */
function insertTextOverImage($imageUrl, $font, $title)
{
    try {
        // Check if ImageMagick extension is installed
        if (!extension_loaded('imagick')) {
            throw new Exception("ImageMagick extension is not installed");
        }

        // Create new Imagick object
        $image = new Imagick($imageUrl);

        // Get the image dimensions
        $imageWidth = $image->getImageWidth();
        $imageHeight = $image->getImageHeight();

        // A few tweeks to the title
        // First replace underscores with space (because I'm too lazy to type %20)
        $title = str_replace("_", " ", $title);

        // Check for ** and split title to title and subtitle
        if (str_contains($title, '**')) {
            $title = explode("**", $title);
            $subTitle = $title[1];
            $title = $title[0];
        }

        // Create a new drawing object
        $draw = new ImagickDraw();

        // Set the text color to white
        $draw->setFillColor('white');

        // Set the font size and font family, size based on the image dimensions
        $fontSize = min($imageWidth, $imageHeight) * 0.1;
 
        // Set Subheading Size   
        $subTitleSize = $fontSize * 0.8;


        $draw->setFontSize($fontSize);
        $draw->setFont($font);
 
        // Set the gravity to center
        $draw->setTextAlignment(Imagick::ALIGN_CENTER);
        // $draw->setGravity(Imagick::GRAVITY_CENTER);

        // Calculate the X position to draw the text 
        $textX = $imageWidth / 2;
        // First if a subtitle exists then get a few measurements:

        if (empty($subTitle)) {
            $textY =  ($imageHeight+$fontSize/2) / 2   ;
        }
        else
        {   //A lot of this is development for adding text wrap to longer lines : todo
            $titleMetrics = $image->queryFontMetrics($draw, $title);
            $subTitleMetrics = $image->queryFontMetrics($draw, $subTitle);
            $titleHeight = $titleMetrics['textHeight']; // The height of the title to be used later to drop the position of the subtitle
            $totalHeight = ($titleMetrics['textHeight'] + $subTitleMetrics['textHeight']);
            $textY =  ($imageHeight+$fontSize) / 2.5   ;
         }  

        // Draw the text on the image
        $image->annotateImage($draw, $textX, $textY, 0, $title );

        // Add the subTitle if it exists
        if (!empty($subTitle)) {

        $draw->setFontSize($subTitleSize);
        $image->annotateImage($draw, $textX, $textY+$fontSize*1.1, 0, $subTitle);
        
        }


        // Resize the image to 1200 x 630 pixels
        $image->resizeImage(1200, 630, Imagick::FILTER_LANCZOS, 1);

        // Save the modified image
        $outputPath = 'generated/' . sanitizeFileName($title) . '.jpg';
        $image->writeImage($outputPath);

        // Destroy the image objects
        $image->clear();
        $image->destroy();

        return $outputPath;
    } catch (Exception $e) {
        // Log the error
        error_log("Error: " . $e->getMessage());

        return "";
    }
}

/**
 * Sanitizes a file name by removing special characters and limiting the length to 25 characters.
 *
 * @param string $fileName The file name to be sanitized
 * @return string The sanitized file name
 */
function sanitizeFileName($fileName)
{
    // Remove special characters
    $sanitizedFileName = preg_replace('/[^A-Za-z0-9\-]/', '', $fileName);

    // Limit the length to 25 characters
    $sanitizedFileName = substr($sanitizedFileName, 0, 25);

    return $sanitizedFileName;
}

// Check if the generated folder exists, create it if not
if (!is_dir('generated')) {
    mkdir('generated');
}

// Check if there are any images older than 1 minute in the generated folder and delete them
$files = glob('generated/*');
foreach ($files as $file) {
    if (time() - filemtime($file) > 60) {
        unlink($file);
    }
}

// Generate the modified image and output it
$result = insertTextOverImage($imageUrl, $font, $title);
header('Content-type: image/png');
readfile($result);

 
2 Likes

Thank you so much for your contribution!

Glad you found it useful. The latest version I have now does also use underscores for spaces and actually disguises the PHP script as a .jpeg file.

I’ve kept meaning to update this but wasn’t sure if anyone still used it.

1 Like

Sounds interesting.

I was thinking about adding a text wrap for multiple lines, but as I add the SEO/Meta etc for clients, I think it might not be necessary, although I do like the challenge :slight_smile:

1 Like

1 Like