PHP Image Rendering


There was a huge amount of fuss getting our image rendering code to work reliably - here are a few tips we picked up!

As part of the FreePoll.net offering, we decided that we wanted to be able to have our users share snapshots of the polls that were currently running quickly and easily. And whilst a lot of people are using 'PrintScreen' or the Microsoft 'Snipping Tool' this wouldn't give the slick user experience that we were looking for.

What we wanted was dynamic generation of an image snapshot, but initially we had no idea the best way to do this and looked at everything from on-server HTML rendering using a WebKit wrapper to on-client snapshots generated in the browser. We even explored HTML-to-PDF rendering and then using just-in-time rendering of the PDF into an image! After much research and fiddling with proof-of-concepts we settled on direct PHP image rendering, but this was nowhere near as easy as we had expected.

"We settled on direct PHP image rendering, but this was nowhere near as easy as we had expected."

1. Basic Elements

There are lots of articles online about using additional libraries such as ImageMagick, but we ignored all of those and opted to use the built-in image library in order to maximise compatibility.

Basic functions for manipulation of images were well documented and pretty-straight forward. As usual PHP.net had good function definitions, which we've worked into a useful example taking into account image scaling for insertion.

				
function renderBasicImage() {
	
	// 1200x800 resolution
	$newImage = imagecreate(1200, 800);

	// colors defined in RBG format
	$background = imagecolorallocate($newImage, 255, 255, 255 );
	$blueColor = imagecolorallocate($newImage, 51, 122, 183);

	// an image loaded from a local file - note the function is type-specific -  
	// create functions for JPG, GIF, BMP and other images also exist.
	$insertableImage = imagecreatefrompng("insertableImage.png"); 

	// using a resize factor to ensure the picture is scaled appropriately.
	// We've hard-coded the resolution to be 600x400, which is the original resolution
	// the $insertableImage but imageresolution() could be used.
	// EDIT: imagescale() is also an efficient function for resizing images after loading.
	$insertScale = 0.5;
	imagecopyresized($newImage, $insertableImage, 25, 0, 0, 0, (600 * $insertScale), (400 * $insertScale), 600, 400);

}
				
			

Of course our rendering required some text, which did cause some complications...

2. Picking a Font Format

The built-in PHP library has multiple methods for loading fonts be able to write text in a prettier format - FT, TTF, GD or PS. These are for FreeType, TrueType, GifDraw and PostScript Type 1 respectively.

Before we even got started, we discounted PostScript functions as these are documented to have been removed in PHP 7.0.0 and so would cause headaches once our whole ecosystem is running on PHP7. GifDraw also looks like a major pain as the GD format takes its endian-ness from the system architecture, meaning that the GD compiled fonts have to be generated on the system you are using them on and even then the resolution is pants. Note also that there are no 'imagegd...' functions but loadfont() seems to run on the GD standard. Needless to say we abandoned GD quickly as well.

Of FreeType and TrueType, frankly we went for TTF because we knew more about it and could easily source the fonts from our system. They're also easily transported around between architectures - so we can continue to develop on Windows and Linux as we see fit and deploy to the live Linux server with no concerns about compatibility.

3. Font Issues

So having decided on the TTF font format we about dutifully coding only to come a couple of errors - one of which seems frequently occuring, and another that there wasn't much information about at all. In any case we fixed both!

Looking online, there are loads of articles about setting environment variables or even dropping fonts into system folders - all of which we found unhelpful.

A common problem with fonts is the system being unable to find them. Looking online, there are LOADS of articles about setting environment variables or even dropping fonts into system folders - all of which we found unhelpful. On the PHP.net page, there are also references to preceding forward-slashes affecting the way the subsystem interprets the filenames. Again, we found this unhelpful as a lot of our development is done on Windows-based machines with local WAMP servers so there aren't any forward slashes! And the last thing we would want to do is maintain different code for both testing/development and deployment. The final solution that worked for us was as follows:

				
function renderTextImage($textContents) {
	
	// 1200x800 resolution
	$newImage = imagecreate(1200, 800);

	// colors defined in RBG format
	$background = imagecolorallocate($newImage, 255, 255, 255 );
	$blueColor = imagecolorallocate($newImage, 51, 122, 183);

	// this is the font file, which is contained in the same directory as the script that is executing.
	// note no slashes and the present file extension.
	$font = "arial.ttf";

	// this little subroutine test-renders the text and incrementally scales the font
	// down so that it will fit within the specified width - in this case within 1100 pixels
	$qWidth = 100000;
	$qFontSize = 30;
	while($qWidth > 1100) {
		$qFontSize--;
		$qWidth = getTextWidth($qFontSize, $textContents, $font);
	}

	// bizarrely - we're using the imagefttext() to draw TTF fonts. I can't find any explanation
	// of why this works, but it does!
	imagefttext($newImage, $qFontSize, 0, 60, 170, $blueColor, $font, $textContents);

}

// this is a helper function that helps us test-render the text
// it uses a function imagettfbbox() which is well-documented on PHP.net
function getTextWidth($fontSize, $text, $font) {
	$bounds = ImageTTFBBox($fontSize, 0, $font, $text);
	$width = abs($bounds[4]-$bounds[6]);
	$height = abs($bounds[7]-$bounds[1]);
	return $width;
}
				
			

You can see within the code we're using some odd nuanced calls to seemingly disparate font functions, however we can confirm that this strategy works (we're using a more complex version of the above code in our live distribution).

Aside from the path-resolution excitement we discussed above, we came across a more nuanced issue. There are subtle differences in the warning messages you'll receive when there are problems with the font file and it's not clear what they mean. After many hours of fiddling we worked out the following:

Invalid Font Filename - this means that the font file could not be found at the path specified. Check spellings and make sure the font is in the correct directory etc.

Could Not Read Font - this means that the font file exists where you say it does, but that the format itself is one that cannot be used. This will mean that you need to pick a different font file.

In my opinion, those two errors effectively say the same thing - both implying that the font file isn't there. Clearly that isn't the case!

In terms of font files that work - we copied the Arial and Arial-variant ttf fonts from the Windows fonts folder and they worked great. Some other Google Fonts didn't work. It's not clear why this is the case, but we found the Windows fonts to be plenty for our use case.

4. Tips And Tricks

Finally we have some little bits of advice for appropriately distributing your image online.

Firstly, you'll need some code to appropriately tell the browser what data you are sending as it will probably infer a text/html MIME Type for the content coming from a .php file. You can tell it all about your content with the following code:

				
$newImage = imagecreate(1200, 800);
header( "Content-type: image/png" );
imagepng($newImage);
imagedestroy($newImage);
				
			
Note that this is type-specific and alternative functions exist for the different image types. We found that PNG provided us with better quality and less compression artifact than with JPEG.

Secondly, we experimented with how we were going to be able to easily distribute our beautifully generated image on social media. We focussed particularly on Twitter and Facebook and noted that they have different ways of deciding how they will create the page summaries that they can display in feeds etc. Twitter uses OpenGraph Cards and twitter-specific tag variants which you can use to specify the image - you can find more details about this here.

Facebook was a slightly more tricky customer and appears to request the page and then parse it to identify image tags from which it can extract images. You therefore need to place the image on the webpage that your users will share so that it can be picked up on the social media site. Worth bearing in mind is how you will format it so that it is not overtly visible in preference to your page content, but also isn't disguised by underhand means such as CSS tags which will make it invisible or setting all it's height and width to zero.

Authored by Tom Johnson. If you have any questions or comments on this blog post, please contact admin@freepoll.net.