Dynamic Image Generation with PHP

      3 Comments on Dynamic Image Generation with PHP

Target Audience
Introduction
Goals of the Tutorial
Background Information

Preparing Images for PHP
Generating Images with PHP
How It Works
  Setting the Birthday
  Preparing the Image
  Drawing Boundaries and Adding Text

  Drawing the Curves
  Sending the Image to the Client
Using the Image
Complete Code
Link Directory
About the Author

Target Audience

This tutorial is designed for the intermediate PHP user.

Introduction

PHP lets you do a lot more than simply generating HTML content, it is also a
great tool to dynamically generate images on-the-fly. For example, you can


  • create buttons with text from a database
  • create grapical displays of statistics from log files
  • enhance business data with graphs and diagrams

This tutorial shows you how to use the GD library to dynamically create
images on your site. The GD library is an external module which is accessed by
a native PHP module.

Goals of the Tutorial

In this tutorial you will learn the following:


  • About headers
  • How to use the basic GD command set to dynamically generate images with
    PHP.

  • How to make use of the HTML header fields to trick your browser into
    thinking your PHP files are actually images


Background Information

The key to controlling how data is displayed is the HTML header. Before
sending out the actual data of your webpage, each Web server sends a header
which is formatted according to the HTTP protocol rules. It denotes the kind of
data, the last modification date, information about the document size and such
(see for example RFC 2068, which can be retrieved at any RFC archive like ftp://ftp.isi.edu/in-notes/rfc2068.txt).

Creating a Header

By default, PHP creates at least one header field for you: the Content-type header which marks the output of your script as HTML data.


<?php
    
print("Hello world!");

?>


$> php hello.php
Content-type: text/html

Hello world!
$> _

As you can see, PHP prints Content-type: text/html and a blank
line before starting to print the output of this script. Everything before the
blank line is defined as being the HTTP header. The header is interpreted by
your browser and doesn’t show up in the page content.

The default header Content-type: text/html is a MIME-type, an
element of a standardized catalog of content types, and denotes the following
content as HTML that your browser is supposed to render. If we’d change this to
“Content-type: text/plain” (another MIME-type), the browser would not render
the data but interprete it as plain text – and simply display it “as is”.

PHP allows you to change the content type field in the header with the
header() function.


header("Content-type: image/jpeg");         // mark the following content as JPEG image
header("Content-type: application/zip");    // mark the following content as ZIP file



Of course, this makes sense only if the data you send is of the specified
type. Sending a header for a ZIP file and printing “Hello world!” after that
would confuse your browser or any other application handling your ZIPs.

But the point is that choosing your own content type headers opens the door
to another dimension of server-side scripting. You can make PHP generate any
form of data which is specified in the catalogue of MIME types (a huge variety
of sound, video and text formats), This tutorial is focused on the dynamic
generation of images.


Note

Optionally, your webserver may add a few fields to the header, but these
are not interesting at the moment.


Preparing Images for PHP

Image generation in PHP can easily be done by using Thomas Boutell’s GD
library. (see http://www.boutell.com/gd) Support for
this library can be compiled into PHP using the –with-gd option
after which you can access the GD functionality with a large set of native
functions. In case you would also like to use the FreeType functions, you
require the FreeType library (for ImageTTFText() and
ImageTTFBox()) that you can retrieve at http://www.freetype.org. The installation of
both should be straight forward when following the documentation.

Generating Images with PHP

Basically, image generation is done in three steps:

  • allocating an image
  • drawing into the allocated space
  • releasing the allocated data in picture format to the browser

Allocating an image is done using GD’s ImageCreate() function.
As parameters this function takes the image width and height in pixels, the
return value consists of a handle that you require to refer to this allocated
image when doing further drawing (or similar) operations.

After allocating an image, you must allocate colors. The GD library needs to
know which colors are in use before it can generate the final image data, thus
it requires you to register all colors that you want to use. Registering colors
can be done using ImageColorAllocate(). As arguments this function
takes the handle of the image for which you want to register a color and the
RGB (red, green, blue) values of the specific color you would like to add. The
return value is a handle for the color which allows you to reference it later
on when drawing.

Example:


$colorHandle imageColorAllocate($image192192192);                    // allocate color

imageFilledRectangle($image00$width 1$height 1$colorHandle);   // use it for drawing



There are a large number of PHP functions for drawing the image. Covering
all the functions is beyond the scope of this tutorial. To get a complete list
of the drawing function, take a look at the appropriate section in the PHP manual. (direct link is here)

Finally, releasing the picture to the browser is done with a single call to
either ImagePNG() or ImageGIF(). The latter is not
supported in current versions of the GD library due to licensing problems with
the compression code. ImagePNG() converts the internal image data
to a PNG file and writes it directly to the client. ImageGIF()

does the same, except for that it creates a GIF image.

Before calling either ImageGIF() or ImagePNG(),
set the content header as shown in the following table.

Image Type Content Header
PNG “Content-type: image/png”
GIF “Content-type: image/gif”

NOTE

The content type headers refer to the entire document. This means that once
you have specified an image content type, you cannot print regular text from
that point on. Thus, after the first byte has been sent out, you cannot change
the header anymore! This means you must first call

header() and then do the rest of your script processing,
otherwise you receive a warning message. If you do not specify a content type
header at all, PHP automatically sends the header type “text/html” for you as
soon as the first character is sent to the browser.

How It Works

To show you how to actually make use of the GD library, this tutorial shows
a sample script to print out your biorythm. Following, each step of the source
is commented and explained.

The theory of the biorythm says that the emotional, physical and
intellectual state of each person has low and high passes within regular
intervals. With birth, all three curves start at zero and then swing with a
certain cycle through your life. If a curve is above the middle line, it is in
an active phase, if it is below it is in a passive phase. If it crosses the
middle line this means a critical day: you’re prone to intellectual, physical
or emotional “catastrophes”. When all three curves cross the line you should
therefore try to avoid to work on important PHP projects.

Each curve has different cycles:

  • Physical: 23 days
  • Emotional: 28 days
  • Intellectual: 33 days

All curves are sinusoid, so drawing them can be reduced to a simple call to
sin() with a little bit of math around it.


Setting the Birthday


This step retrieves the user’s birthdate and calculates the number of
days the person has been alive to date. The result can then be used to
directly calculate the phase of the sine curves for the biorhythm.

Code Flow

  • Checks whether a birthdate has been specified.

  • If not, it displays a form to allow the user to input one.

  • After checking date validity and specifying diagram parameters,
    calculates the number of days the person has been alive to date
    using the Julian calendar.




if(!isset($birthdate))

{
    
/* */
}

$daysGone abs(gregorianToJD($birthMonth$birthDay$birthYear)

                - gregorianToJD(date"m"), date"d"), date"Y")));





Tips
The date calculation uses Julian dates, which count absolute number of
days, not a split date into years, months and days like Gregorian dates
do. In order to determine the number of days the person is alive, both
today’s date and the person’s birthdate have to be converted to Julian
dates. Calculating the difference gives the correct result.

Note

The function gregorianToJD() has been reimplemented in the
PHP source code. Usually, this function is also available in PHP’s
calendaring module, but at the time of this writing, it did not work in
the most up-to-date version of PHP 4. So, in order to assure a working
program with all PHP versions, this function is now “handcrafted”. If you
still want to use the calendaring module, uncomment the function
here.


Preparing the Image


Code Flow

Preparing the image is done with a few calls to a set of GD’s functions.
The following code:


  • Names and creates a new $image of the desired size (in
    pixels).

  • Allocate the colors for the drawing $image.

  • Creates a rectangle with a background color to draw the image.
    (Clears the image space.)



// create image
$image imageCreate($diagramWidth$diagramHeight);

// allocate all required colors
$colorBackgr       imageColorAllocate($image192192192);

$colorForegr       imageColorAllocate($image255255255);
$colorGrid         imageColorAllocate($image000);

$colorCross        imageColorAllocate($image000);
$colorPhysical     imageColorAllocate($image00255);

$colorEmotional    imageColorAllocate($image25500);
$colorIntellectual imageColorAllocate($image02550);

// clear the image space with the background color
imageFilledRectangle($image00$width 1$height 1$colorBackgr);





Tips
It’s a good idea to create a colored rectangle to serve as a background
for drawing the picture. This ensures that the image will be drawn on a
blank space of the color you want. It is also useful for creating a
transparency effect, as this tutorial discusses later.

Note

This tutorial does not include any error checking, which is bad coding
style, but is used for clarity in the tutorial. In a “real” script, there
should be error checking and exception handling to make sure that
everything works as expected, and to give some sort of notification to
the user if there is an error.


Drawing Boundaries and Adding Text


ImageString() writes the specified text string into the
image using a position and size. The first two strings will appear at the
top of the diagram and display the birthdate and today’s date, while the
other three will appear at the bottom and give color information for the
three curves. Working with the predefined color handles makes it easy to
denote which color you want to use.

Code Flow


  • Draws the diagram’s boundaries using a rectangle and the middle
    cross using two simple lines.

  • Places descriptive text in the diagram.



// draw rectangle around diagram (marks its boundaries)
imageRectangle($image00$diagramWidth 1$diagramHeight 20,

               $colorGrid);

// draw middle cross
imageLine($image0, ($diagramHeight 20) / 2$diagramWidth,

          ($diagramHeight 20) / 2$colorCross);
imageLine($image$diagramWidth 20$diagramWidth 2$diagramHeight 20,

          $colorCross);
imageString($image31010,  "Birthday: $birthDay.$birthMonth.$birthYear",

            $colorCross);
imageString($image31026,  "Today:    ".  date(  "d.m.Y"),  $colorCross);

imageString($image310$diagramHeight 42,  "Physical"$colorPhysical);

imageString($image310$diagramHeight 58,  "Emotional"$colorEmotional);

imageString($image310$diagramHeight 74,  "Intellectual",
            
$colorIntellectual);





Figure 1. The final biorhythm image

Drawing the Curves


All the curves are sine waves differing only in their period, drawing a
“rhythm” has been abstracted into the function drawRythm().
The curve is drawn by calculating the biorhythm values on each day from
the start date to the current date, and drawing a line from the value on
one day to the value on the next day. The biorhthm value is a Y position
on the diagram. The lines are drawing in intervals of one day, using as
connection points the X/Y coordinates calculated for each day.

Code Flow

  • Fixes the start angle of the sine wave based on the number of days
    the person is alive ($daysGone) and the period of the
    curve.

  • Calls the drawRhythm function to draw the curve three
    times: one with each parameter set.

  • Within for loop, draws a line from each point to the next point and
    saves the coordinates for the next run of the for loop.



drawRhythm($daysGone23$colorPhysical);
drawRhythm($daysGone28$colorEmotional);

drawRhythm($daysGone33$colorIntellectual);

for($x 0$x &lt;= $daysToShow$x++)

    {
        
// calculate phase of curve at this day, then Y value
        // within diagram

        $phase = (($centerDay $x) % $period) / $period pi();

        $y sin($phase) * (float)$plotScale + (float)$plotCenter;

        // draw line from last point to current point
        
if($x &gt0)

            imageLine($image$oldX$oldY$x $diagramWidth $daysToShow,
            
$y$color);

 
        
// save current X/Y coordinates as start point for next line
        
$oldX $x $diagramWidth $daysToShow;

        $oldY $y;
    }



Tips
It would be better to not draw lines in day intervals on the diagram but
to use smaller steps and interpolate between the coordinates, eventually
even draw pixel-based. This would give a much smoother curve, however,
the compromise in speed and simplicity wasn’t worth it for the purpose of
this tutorial.


Sending the Image to the Client


Now that the image has been created, it needs to be sent to the
client.

Code Flow


  • Send the header, specifying either GIF or PNG as the content
    type.

  • Sets imageGIF or imagePNG command.
    Either command converts the picture data to the target format and
    immediately sends the information to the client. Note that two
    lines are commented out, so you can select the appropriate
    command.
  • Creates an interlaced image.

  • Sets a transparent background color.




header("Content-type:  image/gif");

//header("Content-type:  image/png");

imageGIF($image);
//imagePNG($image);

// create an interlaced image for better loading in the browser
imageInterlace($image1);

// mark background color as being transparent
imageColorTransparent($image$colorBackgr);





Tips
Once you have used the imageGIF or imagePNG,
you cannot make further modifications to the image, as it has already
been sent to the client.

ImageInterlace() allows you to turn interlacing on or off.
This call marks the target image as being an interlaced image (if the
output format supports it). Interlaced images are stored in a different
way, which enables the client browser to show a “preview” of the image
while it is being loaded. At first, you only see a rough “sketch” of the
picture, but the more data is available, the finer and clearer the
picture will become – a very handy feature for large diagrams, loaded
across a slow network connection.

ImageColorTransparent() sets the color which should generate
a transparency effect (again, only if the output format supports it).
This command sets $colorBackgr, the background color, as
transparent color, which creates a transparent “see-through” diagram
background. The background rectangle is being used to create the
transparency effect, meaning, the rectangle is being made transparent –
all spots on the image still having the original rectangle color will be
transparent, allowing to see through to the background color of the HTML
page.

Using the Image

Now that you can make PHP generate image data, you need to know how to use
it. This is actually straightforward: you can use the PHP script just like a
regular image in your HTML files. Because this sample script is “interactive”,
requesting user input first, you need to take a look at it in your browser the
regular http://my.server.net/script.php way. However, for a non-interactive
script, for example, creating an image generated from database inforamtion, all
you’d need to do is to put the following tag into your HTML:

<img
src=”http://my.server.net/script.php”>

Complete Code


<?php

//
// Print a standard page header
//
function pageHeader()
{

    print("<html><head>");
    print(
"<title>Biorhythm with PHP3</title>");

    print("</head><body>");

}

//
// Print a standard page footer

//
function pageFooter()
{

    print("</body></html>");

//
// Function to convert a Gregorian date to a Julian date (number of days)

//
function gregorianToJD($month$day$year)
{
    
// Note: The following algorithm is based

    // on a paper by Peter Baum, pbaum@capecod.net

    if($month 3)
    {

        $month $month 12;
        
$year $year 1;
    }

    $jd $day floor((153 $month 457) / 5) + 365 $year

          floor($year 4) - floor($year 100)
          + 
floor($year 400) + 1721118.5;

    return($jd);

}

//
// Function to draw a curve of the biorythm

// Parameters are the day number for which to draw,
// period of the specific curve and its color

//
function drawRhythm($daysAlive$period$color)
{
    global 
$daysToShow$image$diagramWidth$diagramHeight;

    // get day on which to center
    
$centerDay $daysAlive - ($daysToShow 2);

    // calculate diagram parameters

    $plotScale = ($diagramHeight 25) / 2;
    
$plotCenter = ($diagramHeight 25) / 2;

    // draw the curve

    for($x 0$x <= $daysToShow$x++)
    {
        
// calculate phase of curve at this day, then Y value

        // within diagram
        
$phase = (($centerDay $x) % $period) / $period pi();

        $y sin($phase) * (float)$plotScale + (float)$plotCenter;

        // draw line from last point to current point
        
if($x 0)
            
imageLine($image$oldX$oldY,

                      $x $diagramWidth $daysToShow$y$color);
 
        
// save current X/Y coordinates as start point for next line

        $oldX $x $diagramWidth $daysToShow;
        
$oldY $y;
    }

}

//

// ---- MAIN PROGRAM START ----

// check if we already have a date to work with,

// if not display a form for the user to enter one
if(!isset($birthdate))
{

    pageHeader();

    ?>
     <form method="post" action="<?php print(basename($PHP_SELF)); ?>">

     Please enter your birthday:<br>
     <input type="text" name="birthdate"
            value="MM/DD/YYYY"><input type="submit" value="OK!">
     </form>
    <?php

    pageFooter();

    exit();
}
   
// get different parts of the date

$birthMonth substr($birthdate02);
$birthDay substr($birthdate32);
$birthYear substr($birthdate64);

// check date for validity, display error message if invalid

if(!checkDate($birthMonth$birthDay$birthYear))
{
    
pageHeader();

    print("The date '$birthMonth/$birthDay/$birthYear' is invalid.");

    pageFooter();

    exit();
}
   
// specify diagram parameters (these are global)
$diagramWidth 710;
$diagramHeight 400;    
$daysToShow 30;       

// calculate the number of days this person is alive
// this works because Julian dates specify an absolute number

// of days -> the difference between Julian birthday and
// "Julian today" gives the number of days alive

$daysGone abs(gregorianToJD($birthMonth$birthDay$birthYear)
                - 
gregorianToJD(date"m"), date"d"), date"Y")));

// create image
$image imageCreate($diagramWidth$diagramHeight);

// allocate all required colors

$colorBackgr       imageColorAllocate($image192192192);
$colorForegr       imageColorAllocate($image255255255);
$colorGrid         imageColorAllocate($image000);
$colorCross        imageColorAllocate($image000);
$colorPhysical     imageColorAllocate($image00255);
$colorEmotional    imageColorAllocate($image25500);
$colorIntellectual imageColorAllocate($image02550);

// clear the image with the background color

imageFilledRectangle($image00$width 1$height 1$colorBackgr);

// calculate start date for diagram and start drawing

$nrSecondsPerDay 60 60 24;
$diagramDate time() - ($daysToShow $nrSecondsPerDay)

               + $nrSecondsPerDay;

for ($i 1$i $daysToShow$i++)
{
    
$thisDate getDate($diagramDate);
    
$xCoord = ($diagramWidth $daysToShow) * $i;

    // draw day mark and day number

    imageLine($image$xCoord$diagramHeight 25$xCoord,
              
$diagramHeight 20$colorGrid);
    
imageString($image3$xCoord 5$diagramHeight 16,

                $thisDate"mday"], $colorGrid);

    $diagramDate += $nrSecondsPerDay;
}

// draw rectangle around diagram (marks its boundaries)

imageRectangle($image00$diagramWidth 1$diagramHeight 20,

               $colorGrid);

// draw middle cross
imageLine($image0, ($diagramHeight 20) / 2$diagramWidth,

          ($diagramHeight 20) / 2$colorCross);
imageLine($image$diagramWidth 20$diagramWidth 2$diagramHeight 20,

          $colorCross);

// print descriptive text into the diagram
imageString($image31010,  "Birthday: $birthDay.$birthMonth.$birthYear",

            $colorCross);
imageString($image31026,  "Today:    ".  date(  "d.m.Y"),  $colorCross);
imageString($image310$diagramHeight 42,  "Physical"$colorPhysical);
imageString($image310$diagramHeight 58,  "Emotional"$colorEmotional);
imageString($image310$diagramHeight 74,  "Intellectual",

            $colorIntellectual);

// now draw each curve with its appropriate parameters
drawRhythm($daysGone23$colorPhysical);
drawRhythm($daysGone28$colorEmotional);
drawRhythm($daysGone33$colorIntellectual);

// set the content type
header("Content-type:  image/gif");
//header("Content-type:  image/png");

// create an interlaced image for better loading in the browser
imageInterlace($image1);

// mark background color as being transparent

imageColorTransparent($image$colorBackgr);

// now send the picture to the client (this outputs all image data directly)

imageGIF($image);
//imagePNG($image);

?>


This article has shown the basics of dynamic image generation with PHP in
combination with the GD library. You have learned how to setup PHP, how to deal
with the output data and what basic steps you need to perform in order to
generate your images. A fairly complex example has shown you how to draw
diagrams based on user input making use of GD’s standard functionality. Based
on this code, you are able to perform real life dynamic image generation
now.

Link Directory



About the Author

Till Gerken is a freelance developer and consultant, working for
various companies, focussing on the creation of web applications for Internet
based services. His background during the past 10 years evolved from creating
high-performance multimedia systems such as 3D-engines and realtime sound
mixers using the languages C/C++, Pascal and x86 assembler to developing medium
to large scale dynamic web sites using PHP and its associated tools. Together
with Tobias Ratschiller, he’s currently authoring a book titled Web
Application Development with PHP
which will be published by New Riders
this year. Till is also involved in http://www.phpwizard.net/.