Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Paint 3-D images with PHP

Mike Brittain (mike@mikebrittain.com), Director of Technology, ID Society
Mike Brittain
Mike Brittain is the director of technology at ID Society, a full-service Internet marketing agency in New York City. He has been developing Web sites and applications for more than 10 years, focusing on open source languages and applications. When not at his computer, he can often be found on skis or a snowboard. He can be reached at mike@mikebrittain.com.

Summary:  PHP, a language originally intended for Web development, has been used for years to manage dynamic Web sites and database applications. Extensions to the language available through the PHP Extension and Application Repository (PEAR) have allowed developers to take the language in new and interesting directions. PEAR's Image_3D package is an object-oriented interface for creating three-dimensional (3-D) graphics in a variety of formats, including PNG and SVG, two image formats with increasing support by modern Web browsers. Find out how to use the Image_3D package, learn the limitations of using dynamic 3-D images, and investigate solutions and practical applications of 3-D graphics.

Date:  28 Mar 2006
Level:  Intermediate PDF:  A4 and Letter (240 KB | 37 pages)Get Adobe® Reader®

Activity:  12603 views
Comments:  

Turning to practical examples

So far, the examples you have seen demonstrate the capabilities of this package to generate 3-D images in PHP. Who would have guessed that this language, which was invented to manage Web pages, could be used to generate such sophisticated image files? Well that's all fine and good, but unless you're a 3-D wizard or a calculus hound, you may be yawning. Let's take a look at how you can take your simple objects and command-line scripts and generate some more interesting examples.

Animating 3-D images

The GD graphics library has had a checkered past with respect to support of GIF images. Due to licensing concerns, support for GIFs was removed from GD after V1.6, though it has apparently been restored in V2.0.28. In any case, Image_3D doesn't export animated GIFs, and PNG files don't support animation.

Rather than creating a single file that will play a looped animation, let's see how you can take multiple files to create a flip-book effect. You'll extend the example with the cube of spheres, generating 30 individual files. When viewed in succession, the resulting animation will show the cube rotating around two axes.

You'll use a single script to generate each of the images: $cycles. Just inside the outermost for loop, you will set up the three rotational values that will change for each file, but will be applied to every sphere within the individual files. Your goal is for the animation to loop smoothly, so your rotation should travel through 360 degrees.

For the Y-axis rotation, start with a static rotational value: 30. From there, you add an increment of the 360-degree loop, based on which file you are generating in the sequence. The modulus operator is used to shave off any whole increments of 360 -- a rotation of 375 will be cut back to 15 degrees, staying within the first circle of rotation.

$rot_y = (30 + (int) (360 / $cycles * $i)) % 360;

The hardest part of generating these images has been covered. The rest of the code should look familiar. You create a $world object and give it two light sources. Using three loops, you create the cube of spheres.

Remember that the first cube of spheres that you constructed had five cubes per size, necessitating the creation of 125 3-D objects. You're also creating 30 of these images -- more than 3,000 spheres! In the code below, you have scaled back to four spheres per side and reduced the detail level of each sphere to three (see Listing 14).


Listing 14. Generating 30 flip-book pages
                    
<?php
require_once('Image/3D.php');

$cycles = 30;
for ($i=0; $i < $cycles; $i++) {

    $rot_x = 45;
    $rot_y = (30 + (int) (360 / $cycles * $i)) % 360;
    $rot_z = (15 + (int) (360 / $cycles * $i)) % 360;

    $world = new Image_3D();
    $world->setColor(new Image_3D_Color(255, 255, 255));

    $light1 = $world->createLight(-500, 0, -500);
    $light1->setColor(new Image_3D_Color(255, 255, 0));

    $light2 = $world->createLight(300, -300, -1000);
    $light2->setColor(new Image_3D_Color(255, 162, 0));

    for ($x=0; $x < 4; $x++) {
        for ($y=0; $y < 4; $y++) {
            for ($z=0; $z < 4; $z++) {
                $sphere = $world->createObject('sphere', 
                            array('r' => 25, 'detail' => 3));

                $sphere->setColor(new Image_3D_Color(255, 255, 255));

                $sphere->transform($world->createMatrix('Move', 
                            array(($x * 75) + 50, $y * 75, $z * 75)));

                $sphere->transform($world->createMatrix('Rotation',     
                            array($rot_x, $rot_y, $rot_z)));
            }
        }
    }

    $world->transform($world->createMatrix('Move', 
                                           array(-225, -100, 0)));

    $world->createRenderer('perspectively');
    $world->createDriver('gd');
    $world->render(800, 800, 'animated_png/anim' . ($i+1) . '.png');
}
?>

Run the code from the command line and go make a pot of coffee. This will take a while.

The next step is to stitch all these images together. You'll do that by displaying the first image on an HTML page and using JavaScript to swap through the pile of images. Swapping images on the page is a standard technique, handled by changing the src property of an Image object. Your JavaScript example below sets up the array of image for the flip book, defines a function for swapping the image, writes the first image to the page, and executes the animate() function using the setInterval() timer.


Listing 15. HTML and JavaScript used to create a flip book
                    
<html>
<head>
<title>Animated PNG</title>
</head>
<body>

<script type="text/javascript">
var imageset = new Object();
imageset.seconds = 0.1;
imageset.imgTag = "animation";
imageset.images = new Array();
for ($i=1; $i <= 30; $i++) {
    imageset.images.push('animation_01/anim' + $i + '.png');
}

function animate(animObj) {
    if (!animObj.index) {
         animObj.index = 0;
    }

    animObj.index++;
    if (animObj.index >= animObj.images.length) {
         animObj.index = 0;
    }

    document.images[ animObj.imgTag ].src = 
                                   animObj.images[ animObj.index ];
}

document.write('<img src="' 
              + imageset.images[0] 
              + '" name="animation" />');

setInterval("animate(imageset);", imageset.seconds * 1000);
</script>

</body>
</html>

The resulting animation (see Download) brings your static 3-D images to life.


Viewing dynamic images on the Web

One of the difficulties of generating 3-D images is that it can take a lot of time, memory, and processor cycles to create making them inherently bad for use on Web sites. What can you do to help with these problems?

First off, if you decide to run these scripts from a Web browser, you may find that the server connection times out while you are waiting for the Image_3D package to finish generating the image file. You can turn off PHP's maximum execution time by placing the following line near the top of your script:

set_time_limit(0);

Generating complex3-D images may also eat up a lot of memory in PHP. Running from the command line shouldn't be a problem, but once you run these scripts over the Web, you may find, again, that your server is closing the connection. Make sure you have allotted enough memory to your PHP scripts. You can typically change the value for memory_limit in php.ini or a local .htaccess file.

In all of the examples, Image_3D creates images and saves them to a file. The name of the file can be replaced, however, with one of two PHP output streams:

  • php://stdout
  • php://output

This method failed when you tested on a Windows server, though it's possible that the version of GD did not properly support output streams. So if you have trouble with this method, consider saving the image to file, opening the file and piping the contents back to the output buffer. It may not seem like the most efficient solution, but remember that you're generating 3-D images. File I/O is probably not going to be the bottleneck in this script.

Remember that when generating an image file from PHP, always include the appropriate content type for the server's response headers. In the case of PNG images, use image/png, as shown below:

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


Caching dynamic files

Generating 3-D images on the fly is fine and all, but if you get more than a few hits to the script in an hour, you may cause a big performance dip on your Web server. Let's walk through an example of how you might prevent this.

Imagine that you have a script that creates 3-D images of spheres. Accordingly, you name the script spheres.php. If it only created a sphere of one size and color, it wouldn't be very useful. Suppose you pass two parameters to the script every time you call it: one parameter, radius, will be the r value for creating the sphere object. The second parameter, color, will be a nine-digit RGB value for the color of the sphere. Here is an example of how you might call this script from a Web page:

<img src="sphere.php?radius=20&color=255000000" alt="Red ball" />

Assuming you use a nice detail level of 5 or greater for generating the sphere, you'll probably want to avoid recreating the image every time a visitor comes to your Web page.

The approach you will take is to create a formatted filename for every image you create, which will include the two parameters used to generate the dynamic image -- for example, sphere_20_255000000.png.

Every time the spheres.php script is called, you will first look to see if you have a matching image file that satisfies the request. If so, open the file and pipe the contents to the output stream. If not, build the Image_3D object, save the new image file, then open the file and pipe the contents to the output stream. While the first visitor may have to wait a short period to see the image, future visitors will get the version that has been cached away.

And your system administrator will love you!


Getting your piece of the pie

You're finally at the point where you'll see a really neat example of Image_3D that could be used directly in your business and database applications.

Image_3D supports one more object type that hasn't been introduced yet: a pie chart. Let's jump right in and see what a slice of pie looks like in three dimensions. Each piece of the pie will be a different color, so rather than using white objects and colored lights, you'll use a white light and colored objects.

Start with your standard environment from Listing 1, but replace the two light sources with a single white light (see Listing 16).


Listing 16. A blue 45-degree slice of a pie chart
                    
$light = $world->createLight(0, 1000, 1000);
$light->setColor(new Image_3D_Color(255, 255, 255));

$pie = $world->createObject('pie', array('start' => 0, 
                                         'end' => 45, 
                                         'detail' => 20, 
                                         'outside' => 150));
$pie->setColor(new Image_3D_Color(0, 0, 255));

$world->transform($world->createMatrix('Scale', array(1, 1, 10)));
$world->transform($world->createMatrix('Rotation', array(-60, 0, 0)));

Creating the pie object requires four entries in your array parameter passed to the createObject() method. Start and end refer to the beginning and ending degree measurements of the slice. These are measured from 0 degrees. If the pie was a clock face, 0 degrees lies at the 3 o'clock position, and angular measurement is made in a clockwise direction. Detail provides a level of smoothness for the object, and outside is the relative size of the circular pie.

You use two transformations on this object. The scale transformation is used to give each slice a feeling of depth using a Z-axis size of 10. The rotation applied to the X-axis tilts the pie into the familiar orientation seen in business charts (Figure 18).


Figure 18. A single pie object
A single pie object

As you add slices around the pie, you may find that they overlap each other in an unexpected fashion. This will be simple enough to fix by replacing the GD driver with ZBuffer.

Suppose you are using the Apache Derby database to store customer records for an e-commerce Web site. Each customer record will include the state for each mailing address. A quick SQL query would reveal how many customers live in each state, as shown in Listing 17.


Listing 17. Selecting a count of customer records from distinct states
                    
SELECT COUNT( * ) AS customers, state
FROM customers
GROUP BY state
ORDER BY customers DESC


Designing a class interface

You can easily generate markup that converts the SQL result set into an HTML table. Let's add a fancy pie chart to accompany the table. For this example, start by designing a class interface for how you want your program to build the 3-D chart. Each slice will be defined as a percentage of the whole. You can specify the color of each slice, in case you want to associate those slices directly with data in the HTML table. Finally, an optional third parameter will allow for calling out specific slices by pulling them away from the main pie ($slice->explode);see Listing 18.


Listing 18. Using a pie chart class to build a 3-D image
                    
<?php
require_once 'pie_chart_class.php';

$chart = new PieChart(400, 400, array(255,255,255));

$chart->addSlice(15, array(255,0,0), true);
$chart->addSlice(5,  array(255,255,0), true);
$chart->addSlice(35, array(0,255,0));
$chart->addSlice(30, array(0,255,255));
$chart->addSlice(15, array(0,0,255));

$chart->render('pie.png');
?>

The percentages listed are sample data, only. What you've left out is how you would generate the results set from Derby and translate each count of customers into a percentage of all customers. But that is standard database interaction and somewhat of a tangent to your task at hand.


Constructing the class

Now that you have a sample of the class interface, fill in the gaps by creating the class. There's nothing particularly advanced about this class. The only detail worth noting is that as each slice is added to the pie, it is added in a clockwise direction. For any slices that have the explode property set to true, you will need to calculate the directions along the X- and Y-axes in which you will move the slice to pull it out of the pie (see Listing 19).


Listing 19. PHP class for generating 3-D pie charts
                     
<?php
require_once 'Image/3D.php';

class PieChart {

    private $slices;
    private $width;
    private $height;
    private $bg;
    private $world;

    public function __construct($width, $height, $bg_list)
    {
        $this->width = $width;
        $this->height = $height;
        $this->bg = $bg_list;
    }

    public function addSlice ($percent, $color_list, $explode=false)
    {
        $this->slices[] = new PieChart_Slice($percent, 
                                             $color_list, $explode);
    }

    public function render ($filename)
    {
        $radius = round((min($this->width,$this->height) * 0.85) / 2);

        $world = new Image_3D();
        $world->setColor(new Image_3D_Color($this->bg[0], 
                                        $this->bg[1], $this->bg[2]));

        $light = $world->createLight(0, 1000, 1000);
        $light->setColor(new Image_3D_Color(255, 255, 255));

        $start = 0;
        foreach ($this->slices as $slice) {

            $end = $start + $slice->degrees;

            $options = array('start'   => $start,  
                             'end'     => $end,
                             'detail'  => 20,      
                             'outside' => $radius);
            $pie = $world->createObject('pie', $options);
            $color = new Image_3D_Color($slice->rgb[0], 
                                        $slice->rgb[1], 
                                        $slice->rgb[2]);
            if ($slice->explode) {
                $mid = $end - (($end - $start) / 2);
                $dx = cos(deg2rad($mid)) * ($radius * 0.15);
                $dy = sin(deg2rad($mid)) * ($radius * 0.15);
                $pie->transform($world->createMatrix('Move', 
                                                array($dx, $dy, 0)));
                $color->addLight($color, 0.4);
            }
            $pie->setColor($color);

            $start = $end;
        }

        $world->transform($world->createMatrix('Scale', 
                                                    array(1, 1, 10)));
        $world->transform($world->createMatrix('Rotation', 
                                                    array(-60, 0, 0)));

        $world->createRenderer('perspectively');
        $world->createDriver('zbuffer');
        $world->render($this->width, $this->height, $filename);
    }
}

class PieChart_Slice {

    public $percent;
    public $rgb;
    public $degrees;
    public $explode;

    public function __construct($percent, $color_list, $explode=false)
    {
        $this->percent = $percent;
        $this->rgb = $color_list;
        $this->explode = $explode;
        $this->degrees = 360 * ($percent / 100);
    }
}
?>

The resulting chart is displayed below:


Figure 19. A pie chart with called-out slices in red and yellow
A pie chart with called-out slices in red and yellow

9 of 13 | Previous | Next

Comments



Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=106865
TutorialTitle=Paint 3-D images with PHP
publish-date=03282006
author1-email=mike@mikebrittain.com
author1-email-cc=

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Try IBM PureSystems. No charge.