Making a graphical counter in PHP

Alexey Inkin
11 min readDec 13, 2023

--

This is my first article originally published on September 29, 2004, in Russian. It is now mostly of historical value. Proof of the original date is here.

The image is probably by Eugene Zhdanov, the editor of ProtoPlex magazine where this was first published.

When you see a visitor counter from mail.ru [the most popular service in Russia back then] or any other indicator, you likely want to make your own one. This article will help you achieve that. All you need is web hosting that supports PHP, and some knowledge of this language’s fundamentals. If you doubt that you want it, your website must be among the top cited ones, and you have millions of visitors because even Yandex [another top service in Russia] cares to maintain such things. An indicator is basically a free ad for you, and it does not even annoy visitors!

Why PHP? It is much simpler than Perl, especially in debugging. PHP scripts are less sensitive to the environment and third-party software.

Graphical functions of PHP are in a separate library called GD. Most likely it is built into your PHP distribution, but it won’t hurt to run this script to verify that:

<?php if (function_exists('imageline')) print 'yes'; else print 'no' ?>

function_exists returns true if the function with the given name is defined. In this case, we are checking for one of those graphical functions we will need. If the script shows you do not have it, download the library from here: http://www.boutell.com/gd/ [broken, archive link given]. Back to the subject. I will explain making an indicator on two examples: a download counter of some program and a program’s rating by visitors (I use both on GetSoft.ru). Let’s start with the first one. We will need a background image, on which we will draw numbers. I assume you have an image of 88x31 pixels (standard for such buttons). I use PNG, but you can use any other format. The library supports GIF, JPEG, PNG, BMP, and a few more. By the way, some versions may not support GIF or PNG, so I suggest you check for that first. Use the script above to check for imagecreatefrompng or imagecreatefromgif functions.

“Download from GetSoft.ru” blank button.

We will be printing numbers at the bottom of this image. Create the following script (I call it counterimage.php):

<?php

$hImage = imagecreatefrompng('image.png');
header('Content-type: image/png');
imagepng($hImage);

?>

The first line loads the image into the memory and returns a handle that can be used to work with it. The second line sends a header to be browser that means a PNG image will follow. The third line converts the image in memory to the given format and prints it to the output sending it to the client. Between the first and the third lines, you can do anything you want with the image. We will be drawing digits. There are a few similar functions to work with different image formats, for example, imagecreatefromjpeg and imagewbmp. You can find the reference for them in the PHP manual, search for “image functions”.

If you have a local web server, you can check the script right away. For that, put it into the document root and visit:
http://localhost/counterimage.php

If everything is correct, you should see your image. If you don’t, a lot of things can be wrong. Check if the web server works in the first place by visiting http://localhost

Check where you put the script and the image (in this example, they must be in the same directory). Did you even check if GD is installed :) ?

GD library allows you to print any text on an image, but we will be drawing digits manually to not depend on fonts. We will be using imagesetpixel and imageline functions.

int imagesetpixel (resource image, int x, int y, int color)
int imageline (resource image, int x1, int y1, int x2, int y2, int color)

The first parameter for both functions is the image resource handle that was returned by imagecreatefrompng, the last one is the color to draw with. Its format depends on the representation of color in the image. If it is a 24-bit image, then the color is interpreted as

Blue + Green * 256 + Red * 256 * 256

If it is an 8-bit image that uses a palette, then the color is an index in that palette. I never wanted to bother with a palette, so all my images are 24-bit PNGs, although some readers may accuse me of wasting memory and traffic.

I think the middle parameters are self-explanatory. For imagesetpixel, these are the coordinates of the pixel to color (0, 0 is the top-left corner, if anyone has forgotten :). The parameters of imageline are the coordinates of the points to connect with a line. Fortunately, no one from Microsoft touched this, so lines are drawn regularly. (In Windows GDI, the last pixel is not colored.)

Now we need a function to draw the digits. In PHP, functions can be defined anywhere in the code, but it’s better to not get weird and put it just after <?php. Use this code:

function PrintChar(
$hImage,
$strChar,
$x, $y,
$nColor)
{
switch ($strChar) {
case '0':
imageline($hImage, $x, $y + 1, $x, $y + 4, $nColor);
imageline($hImage, $x + 1, $y + 5, $x + 2, $y + 5, $nColor);
imageline($hImage, $x + 3, $y + 4, $x + 3, $y + 1, $nColor);
imageline($hImage, $x + 2, $y, $x + 1, $y, $nColor);
break;
case '1':
imageline($hImage, $x + 2, $y, $x + 2, $y + 5, $nColor);
imagesetpixel($hImage, $x + 1, $y + 1, $nColor);
break;
case '2':
imagesetpixel($hImage, $x, $y + 1, $nColor);
imageline($hImage, $x + 1, $y, $x + 2, $y, $nColor);
imagesetpixel($hImage, $x + 3, $y + 1, $nColor);
imageline($hImage, $x + 3, $y + 2, $x, $y + 5, $nColor);
imageline($hImage, $x + 1, $y + 5, $x + 3, $y + 5, $nColor);
break;
case '3':
imageline($hImage, $x, $y, $x + 3, $y, $nColor);
imagesetpixel($hImage, $x + 3, $y + 1, $nColor);
imageline($hImage, $x + 1, $y + 2, $x + 2, $y + 2, $nColor);
imageline($hImage, $x + 3, $y + 3, $x + 3, $y + 4, $nColor);
imageline($hImage, $x + 2, $y + 5, $x + 1, $y + 5, $nColor);
imagesetpixel($hImage, $x, $y + 4, $nColor);
break;
case '4':
imageline($hImage, $x + 2, $y + 4, $x, $y + 4, $nColor);
imageline($hImage, $x, $y + 3, $x + 3, $y, $nColor);
imageline($hImage, $x + 3, $y + 1, $x + 3, $y + 5, $nColor);
break;
case '5':
imageline($hImage, $x + 3, $y, $x, $y, $nColor);
imagesetpixel($hImage, $x, $y + 1, $nColor);
imageline($hImage, $x, $y + 2, $x + 2, $y + 2, $nColor);
imageline($hImage, $x + 3, $y + 3, $x + 3, $y + 4, $nColor);
imageline($hImage, $x + 2, $y + 5, $x, $y + 5, $nColor);
break;
case '6':
imageline($hImage, $x + 3, $y, $x + 1, $y, $nColor);
imageline($hImage, $x, $y + 1, $x, $y + 4, $nColor);
imageline($hImage, $x + 1, $y + 5, $x + 2, $y + 5, $nColor);
imageline($hImage, $x + 3, $y + 4, $x + 3, $y + 3, $nColor);
imageline($hImage, $x + 2, $y + 2, $x + 1, $y + 2, $nColor);
break;
case '7':
imagesetpixel($hImage, $x, $y + 1, $nColor);
imageline($hImage, $x, $y, $x + 3, $y, $nColor);
imagesetpixel($hImage, $x + 3, $y + 1, $nColor);
imageline($hImage, $x + 2, $y + 2, $x + 2, $y + 3, $nColor);
imageline($hImage, $x + 1, $y + 4, $x + 1, $y + 5, $nColor);
break;
case '8':
imagesetpixel($hImage, $x, $y + 1, $nColor);
imageline($hImage, $x + 1, $y, $x + 2, $y, $nColor);
imagesetpixel($hImage, $x + 3, $y + 1, $nColor);
imageline($hImage, $x + 2, $y + 2, $x + 1, $y + 2, $nColor);
imageline($hImage, $x, $y + 3, $x, $y + 4, $nColor);
imageline($hImage, $x + 1, $y + 5, $x + 2, $y + 5, $nColor);
imageline($hImage, $x + 3, $y + 4, $x + 3, $y + 3, $nColor);
break;
case '9':
imageline($hImage, $x, $y + 5, $x + 2, $y + 5, $nColor);
imageline($hImage, $x + 3, $y + 4, $x + 3, $y + 1, $nColor);
imageline($hImage, $x + 2, $y, $x + 1, $y, $nColor);
imageline($hImage, $x, $y + 1, $x, $y + 2, $nColor);
imageline($hImage, $x + 1, $y + 3, $x + 2, $y + 3, $nColor);
break;
}
return 5;
}

This function draws a single digit. The first parameter is the image resource handle, and the second one is the character to draw (we will only support digits here). Next are the coordinates of the top-left corner and the color (in whatever format the image functions expect). The return value is how many pixels the output position is shifted to the right afterward. This will allow us to make variable-width symbols, but we will not be using it now. This function draws the letters using my font I sketched up in my breaks from coding:

You can come up with any other font if you do not like GetSoft Sans Serif:) In this image, the crosses mark frames of reference for each digit. The yellow dots are entry points, the blue lines are the moves of a hypothetical brush, and the red lines are the brush strokes. Those readers who are interested in the content and not the form will forgive me for this lame sketch made with Paint.

Next is the function that will take a line and call PrintChar for every character in it:

function PrintLine(
$hImage,
$str,
$x, $y,
$nColor)
{
$nLength = strlen($str);

for ($i = 0; $i < $nLength; $i++)
$x += PrintChar($hImage, substr($str, $i, 1), $x, $y, $nColor);
}

All five parameters match those of the previous function. The difference is that x and y are the coordinates of the top-left corner of the entire line. A few words about the built-in functions:

int strlen (string str)
string substr (string string, int start [, int length])

strlen takes a string and returns its length. substr extracts a fragment and accepts an input string, the fragment’s start, and length (if the last argument is not given, the entire remaining string is returned). This way, we call PrintChar for every character in the string, and every time we shift the output position to the right by increasing x (again, the function returns the width of the drawn character).

Let’s verify all of that code. Add the following before calling imagepng:

PrintLine($hImage, '0123456789', 10, 22, 0);

This call should print every digit we support, one by one. The coordinates of (10, 22) are a guess and will likely differ for your image. Once again, visit http://localhost/counterimage.php, and you should see the following image:

The second image is the real functioning indicator from my website. It additionally prints “+”, and it can align text to the right. To implement that, PrintLine must be improved. I believe you can do this by yourself. Now we need to make this script write a meaningful number like the number of downloads, visits, subscribers, etc. The technical part of that is fully determined by the website structure, so I cannot do this for you. I can only say that you should replace “0123456789” with a specific variable.

The information on the indicator will likely depend on some input parameter, unless your indicator is made for something fixed, like the weather in Nizhny Novgorod, my home (and only there). For that, the script should receive one or more parameters. It will be accessed like this:
http://localhost/counterimage.php?param1=value1&param2=value2

In each script, you have an associative array named $_GET, which stores all parameters of the script. When you need them, you can get them like this:

$a = $_GET{'param1'}; // $a = "value1"

Now, to the second indicator that my website also uses. It will be showing the rating of a piece of software by visitors (as the number of stars from 1 to 5) or “not rated”. The difference from the first indicator is that the star images are stored in separate files. We will be laying them over the background image. Below are the separate images, respectively named star0.gif, star1.gif, notgraded.gif, and image.png. We will be combining them.

“not rated” in Russian.
“Rating at the forum of GetSoft.ru” in Russian.

We will use a new function:

int imagecopy (
resource dst_im,
resource src_im,
int dst_x, int dst_y,
int src_x, int src_y,
int src_w, int src_h
)

The first two parameters are the resource handlers of the images, the destination and the source respectively. The next two are the coordinates of where to put the fragment. Then are the coordinates in the source image to take the fragment from, and the last two are the width and the height of the fragment. It should be noted that our GIF images have transparent pixels. Fortunately, this function is smart enough to handle them correctly and not just copy the entire rectangle, so do not worry about that. Let’s use this blunt code:

$hImage = imagecreatefrompng('image.png');
if ($nMark) {
$hImageStar0 = imagecreatefromgif('star0.gif');
$hImageStar1 = imagecreatefromgif('star1.gif');
$x = 35;

for ($i = 0; $i < $nMark; $i++, $x += 10)
imagecopy($hImage, $hImageStar1, $x, 8, 0, 0, 10, 10);

for (; $i < 5; $i++, $x += 10)
imagecopy($hImage, $hImageStar0, $x, 8, 0, 0, 10, 10);
} else {
$hImageNotGraded = imagecreatefromgif('notgraded.gif');
imagecopy($hImage, $hImageNotGraded, 35, 8, 0, 0, 50, 10);
}

This assumes that nMark contains a number from 0 to 5, and 0 means no rating (every piece of software deserves at least one star :) The first loop draws the desired number of yellow stars, and the second one draws the remaining number of white stars. If there is no rating, we will copy a single image showing that. The result is something like this:

Lastly, I will show the HTML code you should give to your users to put on their websites:

<a target="_blank" href="http://getsoft.ru/">
<img
src="http://getsoft.ru/counterimage.php?id=777"
width="88" height="31"
border="0"
>
</a>

Here you should replace getsoft.ru with your address, and replace 777 with some real parameter value. Basically, that’s it, the rest depends on your imagination. This is my first article, so I am very curious what you think of it. I do not know where it will be published besides ProtoPlex, but if the website you are reading supports commenting, please use that function. If you have ideas of what I should write about, I will be happy to learn them. All the best, and may your indicators be no less popular than mail.ru!

September 29, 2004

Never miss a story, follow my Telegram channel: ainkin_com

--

--

Alexey Inkin

Google Developer Expert in Flutter. PHP, SQL, TS, Java, C++, professionally since 2003. Open for consulting & dev with my team. Telegram channel: @ainkin_com