Complex shapes

int imagefilledpolygon ( resource image, array points, int num_points, int color)

int imagepolygon ( resource image, array points, int num_points, int color)

Beyond rectangles, ellipses, and arcs, which all have predefined shapes, there are polygons: multi-sided shapes of arbitrary geometry. This is where the real fun comes in, because they are more complicated to define and require a little thinking.

The parameter list is straightforward, and the same for both imagefilledpolygon() and imagepolygon() : the image resource to draw on, an array of points to draw, the number of total points, and the colour. The array is made up of pixel positions, where each even number in the array and 0 are X co-ordinates and the odd numbers are Y co-ordinates. PHP uses these co-ordinates sequentially, drawing lines from the first (X,Y) the second, to the third, etc, until drawing a line back from the last one to the first.

The easiest thing to draw is a square, and we can emulate the functionality of imagefilledrectangle() like this:

<?php
    $points = array(
        20, // x1, top-left
        20,

        230, // x2, top-right
        20, // y2

        230, // x3, bottom-right
        230, // y3

        20, // x4, bottom-left
        230 // y4
    );
  
    $image = imagecreatetruecolor(250, 250);
    $green = imagecolorallocate($image, 0, 255, 0);
    imagefilledpolygon($image, $points, 4, $green );
  
    header('Content-type: image/png');
    imagepng($image);
    imagedestroy($image);
?>

I have added extra whitespace in there to make it quite clear how the points work in the $points array. Alas, not all polygons are as easy to draw using imagefilledpolygon() as squares are - working with more points takes a little thinking, and you will find it is easy to make mistakes unless you either have a degree in mathematics or plan out the polygon on graph paper first!

However, there is an easy way around this situation: why hand-code points when you just point and click? On graphics-heavy websites today, the technique of image slicing is common - literally taking one large image, and cutting it up into smaller parts that can have hyperlinks attached to them. However, this was not always the case; in the earlier days of the web, image maps were the way to achieve the same effect, and there is a special attribute that can be used with images that automatically sends to the server the X and Y co-ordinates where the user clicked if the image is hyperlinked. This is still used today now and then, but, as you can imagine, the primary use is for advertising - advertisers like to know what part of their advert the user clicked on as it could give them valuable information about what attracted the user to click.

What we're going to do is allow users to draw their polygon by clicking on the image where they want each point to be. To do this, you will need to rename picture.html to picture.php, as we're going to need to parse the input from our image map into usable X and Y co-ordinates.

Here is the script for picture.php:

<html>
<title>PHP Art</TITLE>
<bodygt;

<?php
    session_start();

    if (isset($_GET['clear'])) {
        unset($_GET['clear']);
        unset($_SESSION['points']);
    }

    if (count($_GET)) {
        $click = array_keys($_GET);
        $click = explode(',', $click[0]);
        $_SESSION['points'][] = $click[0];
        $_SESSION['points'][] = $click[1];
    }
?>
<a href="picture.php"><img src="picture3.php" ismap="ismap" /></a><br />
<a href="picture.php?clear=true">Clear image</a>
</body>
</html>

Before you mentally decode the PHP block, look to the picture - the ISMAP attribute in there is what will make the web browser send co-ordinates across for us. The co-ordinates are simply tacked onto the end of the URL, like this: picture.php?303,38. Naturally that is no good to us as is, so we need to convert that into straight numbers.

Ignore the part about isset($_GET['clear']) for now, and look at the count($_GET) part. This is where the co-ordinates get turned from one string into two integers. First up, we rely on the $_GET array's size to tell us when we have co-ordinates to use - if count() returns anything other than 0, we consider that as meaning there is something to parse, which means you should not pass anything in $_GET that remains set at this point (I will explain that more soon).

First things first, the code takes the keys of the $_GET array, because the co-ordinates are passed in as a key rather than a value. It then calls explode() on the first element of the $_GET keys, which is assumed to be the co-ordinates as nothing else should be there, and uses the comma to split the X,Y format into two individual elements. Finally, we add to the session variable $points, which is the point array, the first element (the X co-ordinate) and the second element (the Y co-ordinate). Thus we've converted the click, and stored it in a session for later use.

Now, back to the isset($_GET['clear']) part. You'll see in the HTML there is a hyperlink beneath the picture that passes "clear=true" back to the page over HTTP GET, and we just said that was a big no-no. In order to allow the user to start their polygonal picture afresh without having to close and re-open their browser, we need to have a link somewhere to clear the image - this is the link. To get around the fact that we do not want things to be in $_GET, we do the isset($_GET['clear']) check - if we find 'clear' is set, we first unset() it so that the $_GET array is now free of dirty data, then we clear the $_SESSION['points'] array, effectively wiping the picture clean.

So far, so good - now onto picture3.php:

<?php
    session_start();

    if (!isset($_SESSION['points'])) $_SESSION['points'] = array();

    $image = imagecreatetruecolor(250, 250);
    $green = imagecolorallocate($image, 0, 255, 0);
    $pointcount = count($_SESSION['points']) / 2;

    switch($pointcount) {
        case 0:
            break;
        case 1:
            break;
        case 2:
            break;
        default:
            imagefilledpolygon($image, $_SESSION['points'], $pointcount, $green );
    }

    header('Content-type: image/png');
    imagepng($image);
    imagedestroy($image);
?>

That looks easy, huh? Well, in fact it is - all the "hard" work has already been done in picture.php, and this is just a matter of taking the points array and drawing it.

First up, note that it sets $_SESSION['points'] if it does not already exist - this stops warnings from appearing, which would of course foul up the image. The next two lines are no surprise, but the third line stores the number of points in the array. As the array always holds two elements for each point, one for X and one for Y, the number of points should always be equal to the size of the array divided by two.

Note that the drawing is now done inside a switch/case block. This is actually quite important, because the definition of a polygon is a shape with three or more points - one point is a dot, and two is a line, both of which cannot be done with imagefilledpolygon(). So, the switch/case block is there to make sure that if the number of points is equal to 0, 1, or 2, nothing happens, but for all other values we draw a polygon with it.

Finally the image is outputted as a PNG, then passed into imagedestroy() for cleanup - all done. The end result? You should now be able to click on the picture to draw any shape you want - keep in mind that you need to place at least three points before your shape appears.

Author's Note: Remember that PHP draws the polygon by iterating sequentially through the points array, and if your shape crosses itself, it is interpreted as a hole in the polygon. If you re-cross the hole, it becomes filled again, and so on.

Using this method is, of course, not always possible - you cannot ask your users to draw their own pictures for your site! However, with just the smallest tweak, you can have your picture generator output its co-ordinates in a format you can use to paste into another script as its point array. The function var_export() is very similar to both var_dump() and print_r(), but with the additional extra that it outputs arrays in a format that can be cut and pasted into PHP nearly perfectly - ideal for our use.

Change picture.php, and add this somewhere:

if (isset($_SESSION['points']) && count($_SESSION['points'])) {
    var_export($_SESSION['points']);
}

Now you should see the contents of points directly on the web page for you to use. Be sure to check the end of the array, though - you may find there is an extra comma there, which needs to be removed before it will work. You can paste that whole array() call into your own script, assign the result to a $points variable, and pass it into imagefilledpolygon() to get exactly the same shape you drew by hand - perfect!

Author's Note: You can specify polygons with any number of points, with the only downside being that it will take longer for PHP to create your image.

 

Want to learn PHP 7?

Hacking with PHP has been fully updated for PHP 7, and is now available as a downloadable PDF. Get over 1200 pages of hands-on PHP learning today!

If this was helpful, please take a moment to tell others about Hacking with PHP by tweeting about it!

Next chapter: Outputting text >>

Previous chapter: More shapes

Jump to:

 

Home: Table of Contents

Copyright ©2015 Paul Hudson. Follow me: @twostraws.