Adding text to an image using PHP and GD

March 1st, 2024

There might be many reasons you need to add text on top of an existing image. The main reason for me was to add the title of a post, on top of a "template" background image to generate SEO images.

The code

I've added comments in the code below to denote what it does. The code uses Laravel for the public_path() helper, but should be fairly straightforward to use native PHP or another framework instead:

// Get the template image that we want to add text to
$image = public_path('social/social-template.png');

// Create a GD Image object
$image = imagecreatefromstring($image);

// Define the color and font size of our text
$navy = imagecolorallocate($image, 20, 45, 111);
$font_size = 128;
$font_path = public_path('fonts/Jokker-Semibold.woff');

// Wrap the text based on a maximum width (see next code block)
$text = $this->wrapText('This is my title', $font_size, $font_path);

// Add the text to the image
imagettftext(
    image: $image,
    size: $font_size,
    angle: 0,
    x: 210,
    y: 550,
    color: $navy,
    font_filename: $font_path,
    text: trim($text)
);

// Store the image
imagepng($image, public_path("social/images/this-is-my-title.png"));

// Clear memory
imagedestroy($image);

Wrapping text

Defining a max width and wrapping the text isn't very straightforward, as each font will result in a different width of the text. Luckily, GD also provides some methods to calculate that:

public function wrapText(string $text, int $font_size, string $font_path): string
{
    // A variable to store our result in
    $wrapped = '';
  
    // Split the text into an array of words
    $words = explode(' ', $text);

    foreach ($words as $word) {
        // Calculate the size of the current result + the additional word
        $teststring = "{$wrapped} {$word}";
        $testbox = imagettfbbox($font_size, 0, $font_path, $teststring);

        // If the test box width is larger than our max allowed width,
        // add a line break before the word, otherwise add a space
        if ($testbox[2] > 1900) {
            $wrapped .= "\n".$word;
        } else {
            $wrapped .= ' '.$word;
        }
    }

    return $wrapped;
}

Alternatives

If you prefer to work with HTML & CSS to generate images, take a look at browsershot

MENU