Tutorials

PHP Add Text To Image

Views: 202584
Rating: 5/5
Votes: 8

Long and boring back-story....

One of the standard features of a message board is allowing members to have a signature, which is appended to the bottom of each post they make. Posters can put whatever they want into the signature (within forum settings). Putting quotes in one's signature is one of the more popular things to do.

A lot of people put in some motto they live by, some funny quote by some famous person, whatever. Some people like to quote things other posters on the board have said. I am one of those people. I think it's fun and promotes a sense of belonging to the community.

I've been a member of the phpfreaks forum for a number of years now, and over the years, I have posted and come across quotes that were (to me, anyways) worth collecting. But having a bunch of quotes in a sig isn't very convenient.

So one day I got the bright idea to pick a random quote from my list and just display that. Well most message boards (or websites in general, for that matter) will not let you just post script and parse it willy nilly for you. Something to do with security or whatever blahblahblah translation: sites are uptight.

But what most sites will let you do, is post images. One of the great things about php is that it comes with something called the GD library that lets you manipulate images in all sorts of ways. And it will even let you output it as an image to a browser, so you can load a php file as if it were an actual image.

Problem solved! I decided to put the quotes I've collected into a database and write a script that picks a random one and creates an image out of it, tell the browser the content is an image and output the image, and blam! I get to post a random quote in my sig!

I weep for the future....

Soon afterwards, I started receiving messages on a fairly regular basis asking me "What kind of sorcery is this?? How do I do that??" You know, I think it's kind of funny people should ask, because this is really nothing new. Remember back in the day when people made those stupid sig images that show "personal" info?

************************************
*     Your IP is xxx.xxx.xx.xx     *
*       Your Host is blahblah      *
*        Your OS is windoze        *
* Your "Shoe Size" is 4 1/2 inches *
*      You "Bat" left handed       *
************************************

On top of that, this is pretty much the same principle as captcha verification on forms, which has been around for years now, and surely you all know what that is, right? And yet, here I am, writing a tutorial about it, because people are asking, so whatever.

This tutorial will not detail how to put quotes (or text in general) into a database or retrieve a random one. You can learn all about basic database interaction in my basic database interaction tutorial. Instead, I'm just going to hard-code a couple of random quotes into an array and pick a random one.

Also, this tutorial is not going to show you how to do anything fancy, like wavy text, frilly borders, or get into distorting methods used in captcha images, etc...; we're going to make a gray background so you can see the image boundaries, and plain old run of the mill white text (to contrast the gray background). I'm here to show you how to start. Where you go from there is up to you.

Code is that way ---------------->

Payment, Up Front

Here is the complete script. Copy and paste it. Run it. Be amazed. My next trick will be to pull a quarter out of your ear.

<?php
// Path to our font file
$font = 'arial.ttf';
$fontsize = 10;

// array of random quotes
$quotes = array(
"http://www.flingbits.com is the greatest invention since sliced bread! Except you can't slice it...",
"Did you hear about the guy whose whole left side was cut off? He's all right now.",
"There was a sign on the lawn at a drug re-hab center that said 'Keep off the Grass'.",
"Police were called to a daycare where a three-year-old was resisting a rest.",
"A hole has been found in the nudist camp wall. The police are looking into it.",
"When a clock is hungry it goes back four seconds.",
"Time flies like an arrow. Fruit flies like a banana.",
"Local Area Network in Australia: the LAN down under.",
"Alcohol and calculus don't mix so don't drink and derive.");

// generate a random number with range of # of array elements
$pos = rand(0,count($quotes)-1);
// get the quote and word wrap it
$quote = wordwrap($quotes[$pos],20);

// create a bounding box for the text
$dims = imagettfbbox($fontsize, 0, $font, $quote);

// make some easy to handle dimension vars from the results of imagettfbbox
// since positions aren't measures in 1 to whatever, we need to
// do some math to find out the actual width and height
$width = $dims[4] - $dims[6]; // upper-right x minus upper-left x 
$height = $dims[3] - $dims[5]; // lower-right y minus upper-right y

// Create image
$image = imagecreatetruecolor($width,$height);

// pick color for the background
$bgcolor = imagecolorallocate($image, 100, 100, 100);
// pick color for the text
$fontcolor = imagecolorallocate($image, 255, 255, 255);

// fill in the background with the background color
imagefilledrectangle($image, 0, 0, $width, $height, $bgcolor);

// x,y coords for imagettftext defines the baseline of the text: the lower-left corner
// so the x coord can stay as 0 but you have to add the font size to the y to simulate
// top left boundary so we can write the text within the boundary of the image
$x = 0; 
$y = $fontsize;
imagettftext($image, $fontsize, 0, $x, $y, $fontcolor, $font, $quote);

// tell the browser that the content is an image
header('Content-type: image/png');
// output image to the browser
imagepng($image);

// delete the image resource 
imagedestroy($image);
?>

And to apply it, you would use a standard img tag (or img bbcode on a forum), pointing to the file as the source, like so:

<img src='http://www.yoursite.com/yourscript.php' />

Hey...you said size doesn't matter!

// Path to our font file
$font = 'arial.ttf';
$fontsize = 10;

First we will decide what font to use and the font size. How you do this will vary from setup to setup. You may have to include a path/to/fontfile for fonts your server may or may not have installed (most hosts have a bunch of fonts pre-installed in some path or other). It's up to you to figure out where that file is, and if the font you use is installed.

If you want to skip all that for now, the easiest hassle free thing to do is to upload your font into the same directory as your script, and the script will start by looking for it there. You will have to do that for any font your host doesn't have, anyways, so may as well cut to the chase.

I chose plain old arial font, and a font size of 10, measured in inches..er...pixels.

..And the next randumb number is...

// array of random quotes
$quotes = array(
"http://www.flingbits.com is the greatest invention since sliced bread! Except you can't slice it...",
"Did you hear about the guy whose whole left side was cut off? He's all right now.",
"There was a sign on the lawn at a drug re-hab center that said 'Keep off the Grass'.",
"Police were called to a daycare where a three-year-old was resisting a rest.",
"A hole has been found in the nudist camp wall. The police are looking into it.",
"When a clock is hungry it goes back four seconds.",
"Time flies like an arrow. Fruit flies like a banana.",
"Local Area Network in Australia: the LAN down under.",
"Alcohol and calculus don't mix so don't drink and derive.");

$quotes is our array of random quotes. Each position of the array is a string value containing a quote that has personal significance to me. Okay not really. But I love puns. Who doesn't?

// generate a random number with range of # of array elements
$pos = rand(0,count($quotes)-1);

We need to pick a random quote from our list of quotes. We can do this by finding out how many elements there are in the $quotes array, and pick a random number between 0 and that number - 1. By default, array positions start at 0, so if there are (in this case) eight quotes, the array keys will be numbered from 0 to 7.

Since count($quotes) returns 8, since there are 8 quotes, we need to subtract 1 from it, so that we pick a random number between 0 and 7.

// get the quote and word wrap it
$quote = wordwrap($quotes[$pos],20);

We then take that number and assign $quotes[$pos] to $quote. So for instance, if $pos was assigned 5, $quote would be assigned:

"Time flies like an arrow. Fruit flies like a banana."

So what's that wordwrap thing? wordwrap() is not strictly necessary for this script. You can remove it and the text will all be on one line. The reason I included it here is because I know that someone will eventually ask how to break up the text so it doesn't make one long image, so I'm making a preemptive strike. I learned it by watching my president.

wordwrap() accepts several arguments. Only the first two are required, and conveniently, that's all we need in this situation, so that's all that's used. The first argument is the string we want to break up, and the 2nd argument is how many characters we want to have on the line before we make a new line.

wordwrap() by default will not make a new line in the middle of a word. If the character length reaches the cutoff and we're in the middle of the word, the entire word is put to the next line. You can force wordwrap() to cut it off at exactly the cutoff point no matter what, by setting the optional 4th argument to true.

Also, by default, wordwrap() "breaks up" the string into new lines by inserting a '\n' after the cutoff point. You can specify what is actually inserted at those points in the optional 3rd argument. There are all kinds of crazy things you can use wordwrap() for that involve taking advantage of being able to specify what is inserted at the cut-off points, but that's a whole other topic of discussion.

One size fits all...?

// create our bounding box for the first text
$dims = imagettfbbox($fontsize, 0, $font, $quote);

In order to create an image, we need to create an image resource. In order to do that, we need to tell php what size we want the image to be. For the purposes of this tutorial, we want the image to be the same size as the text block we just created. The GD Library provides a convenient way to figure out what the "borders" of our text block is: imagettfbbox().

imagettfbbox() calculates the boundaries of the text by making a bunch of weird and archaic calculations based off of things like the text's font size, the angle of the text (like, if we were to want the text to slant at some slope, instead of horizontal, as text is normally read), the font itself, and the text in question. I think it just kind of eyeballs it, but it seems to work, so I'm not complaining.

// make some easy to handle dimension vars from the results of imagettfbbox
// since positions aren't measures in 1 to whatever, we need to
// do some math to find out the actual width and height
$width = $dims[4] - $dims[6]; // upper-right x minus upper-left x 
$height = $dims[3] - $dims[5]; // lower-right y minus upper-right y

Unfortunately, imagettfbbox() doesn't just tell you "Hey, this is the width and this is the height," so we need to figure that out on our own. imagettfbbox() returns an array of different numbers. 8 numbers, to be exact. Each one represents an x,y coordinate for the corners of the text 'boundary'. You can refer to the manual for which array elements refer to which coords. I commented the ones I used, here.

Ideally, all we should need is the bottom left corner, but for some reason the numbers don't quite work out that way. Lower boundaries come back as 0's or -1's, upper boundaries come back a few pixels short; who knows why. Who cares (I'm sure someone does, and I'm sure they'll eventually post a comment about it, and to that person, I say thanks for clearing that up! <coughnerdcoughcough>).

Fortunately, we can nonetheless work with those numbers. We just need to subtract a left x from a right x, and a top y from a bottom y. We'll assign the results in easy to use variables $width and $height, and we're good to go.

A picture is worth a thousand words...

// Create image
$image = imagecreatetruecolor($width,$height);

Okay now we need to create an image resource. It's basically like a canvas: something to paint the picture on, so to speak. Think of it as opening up your favorite paint program, telling it that you want to create a new picture of x,y size. You start it up, paint little stick figures, and eventually save it to a file. Well, the image resource is basically what your masterpiece is, before you save it; the thing in memory the computer messes around with every time you make a change to the picture.

imagecreatetruecolor basically tells php that we want to work with a canvas x width and y height big. We use the width and height of our text boundary we figured out earlier.

// pick color for the background
$bgcolor = imagecolorallocate($image, 100, 100, 100);
// pick color for the text
$fontcolor = imagecolorallocate($image, 255, 255, 255);

The next thing we want to do is select what colors we want to use. It's like when you are in your paint program, and you choose the color you want to use. You aren't actually using it yet, you're just selecting it. That's what we are doing here, except we are storing our choices in variables. We want one color for our background, and one for our font color.

The first argument for imagecolorallocate is the image resource we just created. The next 3 arguments is the red, green, and blue (rgb) values of the color, specified as a 0 to 255 integer (for each one). 0,0,0 means no color intensity, which is a fancy way of saying black. 255,255,255 is full color intensity, or white. 100,100,100 is some random intensity level I chose; an even mix of all 3 colors (or gray).

Hold that pose!

// fill in the background with the background color
imagefilledrectangle($image, 0, 0, $width, $height, $bgcolor);

Now we get down to business and start painting the picture, so to speak. First, we set the background color of the image. We do this by making a filled rectangle, because our image is a rectangle, and making a filled rectangle the same size of our rectangle is pretty convenient for our purposes.

imagefilledrectangle() takes 6 arguments: the image resource, the top left x,y coordinates, the bottom right x,y coordinates, and the fill color. We fill in the blanks with the appropriate variables, and move along.

// x,y coords for imagettftext defines the baseline of the text: the lower-left corner
// so the x coord can stay as 0 but you have to add the font size to the y to simulate
// top left boundary so we can write the text within the boundary of the image
$x = 0; 
$y = $fontsize;
imagettftext($image, $fontsize, 0, $x, $y, $fontcolor, $font, $quote);

The next thing we do is write the text to the canvas. We do this with imagettftext(). This GD function needs 8 arguments:

1 -The image resource
2 - The font size of the text
3 - The angle we want the text to be written (like, if we want it to slant some way, instead of the normal horizontal. Same as with the imagettfbbox() earlier)
4,5 - The x,y coordinate for the base point of the text. This coordinate specifies the lower left corner of the first character in the text.
6 - the color of the font
7 - the font type
8 - the text to 'paint'

Since the 4th and 5th arguments of imagettftext() specify the lower left point of the text, we need to offset the starting point, so that the text is written within the boundary of our canvas. The x coordinate doesn't need to be offset, so that stays at 0, but the y coordinate needs to be offset by the size of the font.

Signed, sealed and delivered

// tell the browser that the content is an image
header('Content-type: image/png');
// output image to the browser
imagepng($image);

// delete the image resource 
imagedestroy($image);

Okay, our image is now created! Now all we have to do is output the results. First, we send a header to the browser, telling it that the content is going to be an image. I chose png image type for the hell of it. You can specify it as a gif or bmp or jpg or whatever; the GD library supports outputting to different filetypes. You'll have to change the content-type in the header call, as well as the appropriate GD function (like imagegif, imagejpeg, etc..).

Then we actually output the image with imagepng(). The only required argument is the image resource. If that's the only argument we specify, it outputs it as a raw image to our browser (which is why we sent the header to the browser). The other arguments can be used for saving the file, the quality, etc.. You can refer to the manual entry for what the other arguments are for and how to use them.

Last thing we do is delete the image resource with imagedestroy(), so that it's not hanging around in the computer's memory anymore. This isn't strictly necessary, as everything is destroyed, memory freed, etc.. blahblahblah when the script is done executing, but it's good programming practice to clean up after yourself. After all, you do wipe your arse when you're done going #2, don't you? Don't you???

The End!

All you have to do from there is treat the file as if it were an image, in an img tag or in an img bbcode on your forum; whatever.

Hopefully this tutorial was easy enough to understand, and you can use it as a stepping stone to do whatever with it. If you are interested in how to make a captcha image, there are a million and one tutorials out there for that, so you should have no problem finding something. I doubt I will be writing one, especially since, as mentioned, the bulk of it is pretty much the same as what was shown here.

On the other hand, there is a lot to explore and discuss about the specifics of captcha, so who knows, maybe I will. Until then,

Happy Coding!

Crayon Violent