Retro Code Sample: Opening Night Countdown Image

Sometime soon I’ll write a bit on my new-ish philosophy about code samples.  For now it’s enough to say that I’m cleaning out that section of my site but there’s one project I wanted to keep documented, so I’m writing it up here and now even though the code is from 2005.

The “Opening Night Countdown Image” was my first foray into using the PHP image functions and the GD2 library.  Based heavily on the code from the Church Sign Generator, it took the hard-coded date of the Detroit Red Wings’ first game back after the 2004-05 NHL lockout and generated an image displaying the number of days remaining until that date.

There were four parts to the project: Two resource image files in .gif format, the actual PHP that mashed those together properly, and a .htaccess file that allowed for a pretty URL for the image.

The .htaccess file was simple enough.  Inside a folder called /nhl_countdown/ it looked as follows:

# General rewrites
RewriteEngine on
Options +FollowSymlinks
RewriteBase /
RewriteRule ^.htaccess$ - [F]

# image rewrite
RewriteRule countdown.gif /nhl_countdown/image.php

Turn rewrites on, allow Symlinks (completely unnecessary), set the rewrite base, block the .htaccess file, and actually rewrite requests for countdown.gif (the pretty filename) to the actual PHP script that generated the image.

The resource images were also pretty simple.



The first was just a static background featuring the team logos and an empty space where the countdown numbers should be. The second was the set of numbers to use in the countdown itself.  Both were .gif for some reason, probably because of the lack of support for .png in 2005.

The code is where it gets fun so let’s get into that.

$date = '2005-10-05';  // SET THE DATE

$parts = explode('-', $date);

$zero_hour = mktime (0, 0, 0, $parts['1'], $parts['2'], $parts['0']);
$today = mktime (date('H'), date('i'), date('s'), date('m'), date('d'), date('Y'));

$difference = $zero_hour - $today;

$s = $difference;
$d = intval($s / 86400);
$s -= $d * 86400;
$h = intval($s / 3600);
$s -= $h * 3600;
$m = intval($s / 60);
$s -= $m * 60;

if ($d <= '0') {
	$days = '0';
} else {
	$days = $d + 1;

We first determine the number of days remaining until gameday. This is horribly inefficient but it’s easy to say that with eight more years of development experience. This isn’t a code review, it’s a sample, so I’m not going to correct myself.

/* This is the list of allowed characters. The reason more characters aren't allowed is because I didn't put more characters in the characters image. More characters means more figuring out coordinates, ie. more work. Each character array comprises the character itself, its X coordinate (the Y coordinate is always 0), and its width (the height is always 12, the height of the image). */

$allowed_chars = array(
	array('1', 0, 33),
	array('2', 33, 35),
	array('3', 68, 32),
	array('4', 100, 35),
	array('5', 135, 32),
	array('6', 167, 33),
	array('7', 200, 31),
	array('8', 231, 34),
	array('9', 265, 32),
	array('0', 297, 33),

Next we map out what parts of the number resource file relate to each actual number. This is another thing I’d probably do differently now.

/* load the characters image and the background image into memory */
$chars_img = imagecreatefromgif('numbers.gif');
$background_img = imagecreatefromgif('background.gif');

/* get the width and height of the background image */
$src_w = imagesx($background_img);
$src_h = imagesy($background_img);

/* create a new image, that will be output to the browser */
$output_img = imagecreatetruecolor($src_w, $src_h);

/* copy the background image onto the output image */
imagecopy($output_img, $background_img, 0, 0, 0, 0, $src_w, $src_h);

/* Since the letters are centered on the background, the x centerpoint gives a starting point from which the x offset for each letter can be calculated. The y offset is simply the distance from the top of the image to the top of the first row of letters. */
$x_center = 300;
$y_offset = 27;

Then we do a little setup, getting the image resources loaded in and getting ready to do our typesetting.

/* This calculates the x offset for the text. It loops through the letters of the text and adds the width of each letter to the x centerpoint to get the beginning point. */

$x_offset = 0;

// calculate offset
for ($i = 0; $i < strlen($days); $i++) {
	/* Get the current character */
	$curchar = substr($days, $i, 1);

	/* loop through the characters array until we reach the one matching the current character, and add half its width to the x offset. */
	foreach($allowed_chars as $char) {
		if ($curchar == $char[0]) {
			$x_offset += ceil($char[2] / 2);

for ($i = 0; $i < strlen($days); $i++) {
	/* The second pass actually copies each letter from the characters image onto the output image. */
	$curchar = substr($days, $i, 1);

	foreach($allowed_chars as $char) {
		if ($curchar == $char[0]) {
			/* The imagecopymerge() function copies a rectangular area from one image onto another image. This is documented more than adequately on */
			imagecopymerge($output_img, $chars_img, ($x_center - $x_offset), $y_offset, $char[1], 0, $char[2], 45, 100);
			$x_offset -= $char[2];

We loop through each digit in the number of days remaining twice. First to find the size of the image representing the character so we can properly center the completed text, then to actually add said text to the new image.

/* Now that the image is built, it gets sent to the browser. First, send out HTTP headers to tell the browser a GIF image is coming. */
header("Content-Type: image/gif");
header("Content-Disposition: inline; filename=countdown.gif");

/* The imagegif() function sends the output img to the browser. */

/* Housekeeping functions - destroy the scratch images to free up the memory they take up. */

Then we actually output the new image and do some cleanup.

As I said, it’s old, horribly inefficient code. As I’m cleaning up my code samples, though, this is something I didn’t want to lose (perhaps as a reminder of how far I’ve come) so I thought it made sense to write up a post on it.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.