Using PHP & GD to generate images

  • : Function eregi_replace() is deprecated in /nfs/cristina/home1/b/brian/public_html/drupal-6.28/includes/common.inc(1654) : eval()'d code on line 24.
  • : Function eregi_replace() is deprecated in /nfs/cristina/home1/b/brian/public_html/drupal-6.28/includes/common.inc(1654) : eval()'d code on line 35.
  • : Function eregi_replace() is deprecated in /nfs/cristina/home1/b/brian/public_html/drupal-6.28/includes/common.inc(1654) : eval()'d code on line 52.

From the PHP manual:

PHP is not limited to creating just HTML output. It can also be used to create and manipulate image files in a variety of different image formats, including gif, png, jpg, wbmp, and xpm. Even more convenient, PHP can output image streams directly to a browser. You will need to compile PHP with the GD library of image functions for this to work. GD and PHP may also require other libraries, depending on which image formats you want to work with.
To read more about the GD library and compiling it into PHP, check out these resources:
PLEASE NOTE! The imagecolorallocatealpha function used in this script requires GD 2.0.1 or later (2.0.28 or later is recommended). Failure to use more current versions of these libraries can cause numerous errors and server load.

In the demo, we're dynamically setting the text of the tab and what I refer to as scope, which is handy-dandy way of referencing certain tab styles. The styles shown are just examples and I'm happy to provide them to get you started... but you'll likely want to create your own tab designs.

But let's get to the nitty-gritty, which is the code to generate the tabs in the first place:

<?php
/* tabs.inc
 * Email: Brian Barrett <brian [at] cristina [dot] org>
 *
 * DESCRIPTION
 *
 * This code is included into tabs.php and outputs a 24bit PNG tab image with
 * transparency support.  By passing in a scope (e.g. sky_blue, yellow_gradient)
 * the script will use different source images to compile the tab.
 * 
 * Source files expected:
 * 
 *      left_first_active.png           left-most of the first active tab
 *      left_active.png                 left-most (usually rounded corner) of the tab
 *      middle_active.png               single-pixel wide image to tile in the middle
 *      right_active.png                left-most (usually rounded corner) of the tab
 * 
 *      left_first_inactive.png         left-most of the first tab (no overlay shadow)
 *      left_inactive.png               left-most (usually rounded corner) of the tab
 *      middle_inatictive.png           single-pixel wide image to tile in the middle
 *      right_inactive.png              left-most (usually rounded corner) of the tab
 * 
 *      tab_shadow.png                  included in image_base but up to you to put on page
 *      tab_bg.png                      included in image_base but up to you to put on page
 *
 *
 * Also included is a cache_image() function that will tell the browser to
 * cache the returned image for 3 hours (default) for improved performance.
 *
 *
 * REQUIRED
 *
 *      text    text to print on the tab (urlencode funky chars)
 *      scope   type of tab to display, current options include:
 *                      "yellow_gradient" (active orange-yellow gradation, inactive white)
 *                      "default" (white live, grey stale)
 *
 * OPTIONAL
 *
 *      is_live         make tab active - true/false
 *      is_first        is first tab in row - true/false
 *
 *
 * INCLUDES
 *
 *
 * GLOBAL
 *
 *
 * FUNCTIONS
 *
 *      get_tab_image($text,$scope,$is_live,$is_first)
 *      load_config($file_path)
 *      cache_image($seconds)
 *      rgb2hex($hex_or_rbg)
 *
 *
*/
 
 
if (!function_exists('get_tab_image')) {
function get_tab_image($text = "TAB",$scope = "default",$is_live = false,$is_first = false) {
 
        $image_base = "/images/tabs/";
 
        // Get config file..
        $config = load_config("{$image_base}{$scope}/config.txt");
 
        // Register box
        $box = imagettfbbox ($config[fontsize], 0, $config[font], $text);
 
        // Find out the width and height of the text box
        $textW = $box[2] - $box[0];
        $textH= $box[3] - $box[5];
 
        // Set image dimensions
        $width = $textW + ($config[paddingx]*2);
 
        // Setup some moderator variables...
        if (!$is_live) {
                $mod = "_inactive";
        } else {
                $mod = "_active";
        }
        if ($is_first) {
                $mod2 = "_first";
        }
 
        // Create the left side of the tab and get size...
        $img_left = imagecreatefrompng($config[image_base] . "left{$mod2}{$mod}.png");
        $left_x = imagesx($img_left);
        $left_y = imagesy($img_left);
 
        // Create the right side of the tab and get size...
        $img_right = imagecreatefrompng($config[image_base] . "right{$mod}.png");
        $right_x = imagesx($img_right);
        $right_y = imagesy($img_right);
 
        // Create the middle of the tab and get size...
        $img_middle = imagecreatefrompng($config[image_base] . "middle{$mod}.png");
        $middle_x = imagesx($img_middle);
        $middle_y = imagesy($img_middle);
 
        // Use the middle (tiled) image to get overall height...
        $height = imagesy($img_middle);
 
        // Bottom left corner of text...
        $textx = ($config[paddingx]*2)/2;
        $texty = $height - $config[paddingy]/2;
 
        // Create the image...
        $img = imagecreatetruecolor($width,$height);
 
        // Define active and inactive text/shadow colors...
        if ((strlen($config[active_color]) < 10) && !strstr($config[active_color], ",")) {
                $config_active_color = rgb2hex($config[active_color]);
        } else {
                $config_active_color = explode(",",$config[active_color]);
        }
        if ((strlen($config[inactive_color]) < 10) && !strstr($config[inactive_color], ",")) {
                $config_inactive_color = rgb2hex($config[inactive_color]);
        } else {
                $config_inactive_color = explode(",",$config[inactive_color]);
        }
        if ((strlen($config[shadow_active_color]) < 10) && !strstr($config[shadow_active_color], ",")) {
                $config_shadow_active_color = rgb2hex($config[shadow_active_color]);
        } else {
                $config_shadow_active_color = explode(",",$config[shadow_active_color]);
        }
        if ((strlen($config[shadow_inactive_color]) < 10) && !strstr($config[shadow_inactive_color], ",")) {
                $config_shadow_inactive_color = rgb2hex($config[shadow_inactive_color]);
        } else {
                $config_shadow_inactive_color = explode(",",$config[shadow_inactive_color]);
        }
        $active_color = imagecolorallocate($img,$config_active_color[0],$config_active_color[1],$config_active_color[2]);
        $inactive_color = imagecolorallocate($img,$config_inactive_color[0],$config_inactive_color[1],$config_inactive_color[2]);
        $shadow_active_color = imagecolorallocate($img,$config_shadow_active_color[0],$config_shadow_active_color[1],$config_shadow_active_color[2]);
        $shadow_inactive_color = imagecolorallocate($img,$config_shadow_inactive_color[0],$config_shadow_inactive_color[1],$config_shadow_inactive_color[2]);
 
        // Setup image transparency...
        imagealphablending($img, false);
        $colorTransparent = imagecolorallocatealpha($img, 0, 0, 0, 127);
        imagefill($img, 0, 0, $colorTransparent);
        imagesavealpha($img, true);
 
        // Fill image with background color...
        imagefill($img,0,0,$bgcol);
 
        // Fill center of tab with the middle image...
        $i = $left_x;
        while ($i < ($width - $right_x)) {
                imagecopy($img, $img_middle, $i, 0, 0, 0, $middle_x, $middle_y);
                $i = $i + $middle_x;
        }
 
        // Append left cap to the tab...
        imagecopy($img, $img_left, 0, 0, 0, 0, $left_x, $left_y);
 
        // Append right cap to the tab...
        imagecopy($img, $img_right, $width - $right_x, 0, 0, 0, $right_x, $right_y);
 
        // Reset alpha blanding mode so text overlays properly...
        imagealphablending($img, true);
 
        // Write text...
        if ($is_live) {
                // Write drop-shadowed text first...
                if ($config[shadow_active_offsetx]) {
                        imagettftext($img, $config[fontsize], 0, $textx+$config[shadow_active_offsetx], $texty+$config[shadow_active_offsety], $shadow_active_color, $config[font], $text);
                }
                imagettftext($img, $config[fontsize], 0, $textx, $texty, $active_color, $config[font], $text);
        } else {
                // Write drop-shadowed text first...
                if ($config[shadow_inactive_offsetx]) {
                        imagettftext($img, $config[fontsize], 0, $textx+$config[shadow_inactive_offsetx], $texty+$config[shadow_inactive_offsety], $shadow_inactive_color, $config[font], $text);
                }
                imagettftext($img, $config[fontsize], 0, $textx, $texty, $inactive_color, $config[font], $text);
        }
 
        // Set header info (including cache-control)...
        //header("Content-type: image/png");
        cache_image();
 
        // Send the image and cleanup...
        imagepng($img);
        imagedestroy($img);
}}
 
if (!function_exists('load_config')) {
function load_config($file_path) {
        $stored = file($file_path);
        foreach ($stored as $line) {
                if ( (substr($line,0,1) != ";") && (substr($line,0,1) != ";") && (substr($line,0,1) != "#") ) {
                        $pair = explode(":",$line);
                        $config[trim($pair[0])] = str_replace(";","",str_replace("\"","",str_replace("'","",trim($pair[1]))));
                }
        }
        return $config;
}}
 
if (!function_exists('cach_image')) {
function cache_image($seconds = 10800) { // default = 3 hrs
        header('Expires: ' . gmdate('D, d M Y H:i:s',time()+$seconds) . ' GMT');
        header("Cache-Control: max-age=$seconds");
        header('Pragma:');
        header("Content-type: image/png");
}}
 
if (!function_exists('rgb2hex')) {
function rgb2hex($c){
        if(!$c) return false;
        $c = trim($c);
        $out = false;
        if(preg_match("/^[0-9ABCDEFabcdef\#]+$/i", $c)){
                $c = str_replace('#','', $c);
                $l = strlen($c);
                if($l == 3){
                        unset($out);
                        $out[0] = $out['r'] = $out['red'] = hexdec(substr($c, 0,1));
                        $out[1] = $out['g'] = $out['green'] = hexdec(substr($c, 1,1));
                        $out[2] = $out['b'] = $out['blue'] = hexdec(substr($c, 2,1));
                }elseif($l == 6){
                        unset($out);
                        $out[0] = $out['r'] = $out['red'] = hexdec(substr($c, 0,2));
                        $out[1] = $out['g'] = $out['green'] = hexdec(substr($c, 2,2));
                        $out[2] = $out['b'] = $out['blue'] = hexdec(substr($c, 4,2));
                }else $out = false;
                           
        }elseif (preg_match("/^[0-9]+(,| |.)+[0-9]+(,| |.)+[0-9]+$/i", $c)){
                if(strstr($c, ","))
                        $e = explode(",",$c);
                else if(strstr($c, " "))
                        $e = explode(" ",$c);
                else if(strstr($c, "."))
                        $e = explode(".",$c);
                else return false;
 
                if(count($e) != 3) return false;
 
                $out = '#';
                for($i = 0; $i<3; $i++)
                        $e[$i] = dechex(($e[$i] <= 0)?0:(($e[$i] >= 255)?255:$e[$i]));
 
                for($i = 0; $i<3; $i++)
                        $out .= ((strlen($e[$i]) < 2)?'0':'').$e[$i];
 
                $out = strtoupper($out);
        }else $out = false;
 
        return $out;
}}
 
?>

The above code can be put into an include file (usually in a sub-directory like /includes/tabs.inc) and then we create a simple tabs.php script that takes in passed variables and calls the get_tab_image() function:

<?php
 
include ("includes/tabs.inc");
if (!$_GET[scope]) { $_GET[scope] = "yellow_gradient"; }
 
@get_tab_image($_GET[text],$_GET[scope],$_GET[is_live],$_GET[is_first]);
 
?>

This also gives you an easy way of specifying a default scope (tab style) like I've done above. You could just as easily put everything into one tabs.php script... it might even be faster on execution time, although I haven't done any formal testing of that.

Each tab or button is configured using a config.txt file stored in the directory that contains the component images for that button/tab (e.g. /images/tabs/yellow_gradient/config.txt).

Here's an example of one of the config files used on this site:

image_base: "/images/tabs/sky_blue/";
font: "/includes/verdanab.ttf";
fontsize: 10;
paddingx: 14;
paddingy: 11;
active_color: #FFFFCC;
inactive_color: #E6E6E6;
shadow_active_color: #000000;
shadow_active_offsetx: 1;
shadow_active_offsety: 1;
shadow_inactive_color: #000000;
shadow_inactive_offsetx: 1;
shadow_inactive_offsety: 1;

Originally, I had all the different configuration parameters hard-coded within the tabs.inc but that quickly became cumbersome with so many examples. Loading the tab or button config options from a text file isn't the best method if you're looking for the absolute fastest generation times but it was a necessary evil here. If your site will only contain a few button or tab styles, I'd recommend hard coding the config options in the script.

Next, let's get started creating the tab source images...