Image creation with PHP
First published at Tuesday 11 December 2007
Warning: This blog post is more then 17 years old – read and use with care.
Image creation with PHP
Table of Contents
There are several ways to create images or graphics with PHP. First there are some well known extensions, like ext/GD, or perhaps ext/ming everybody immediately remembers, when it comes to graphics generation. But there are several structural differences not in between the avaliable libraries, but also between the image formats you can create. This article series will give you some insight on the formats and libraries, and then shows how an abstraction layer could be build.
Introduction
General notes, relevant for this article.
Terms
Some terms I use in a special way.
- Image
- I use it as a generalization of pictures and graphics.
- Picture
- Images with natural contents, like photos or drawings. Usually there are no or only few clear borders in those images.
- Graphic
- Computer generated graphics with technical illustrations or charts. They often contain clear borders.
Agenda
The sixth part of the article finally covers additional tool classes which will proof helpful during image creation.
Describes the advantages and drawbacks of the different extensions and the formats they can generate.
Describes the basic required data structure and the generation of a first simple shape.
Describes how you can add radial and linear gradients to your generated graphics with each of the backends.
Integrating bitmaps with the backends.
The basics of text rendering with all extensions.
With a small set of tools the automatic generation of images gets a lot simpler and allows you the construction of nice images using the existing APIs.
The code
I provide completely working code within this article, which will not be developed any further, because there are already existing packages, which try to provide such an abstraction layer, like Image_Canvas in PEAR. In the graph component from the eZ Components I personally develop very similar backends under the New BSD license, which will stay limited to the drawing methods we need for the graph component for now.
The complete source code can be downloaded here, or partially copied from the code examples. The source code is not provided under some OpenSource license, but stays under my personal copyright, like the article does. If you want to take the code and continue developing it, please send me a mail and we can discuss this.
The code is written for PHP 5.3, which can currently be compiled from the CVS and makes use of the namespaces features in 5.3.
To run the provided code, you need at least the following extensions installed:
GD (with PNG and FreeType 2 support)
cairo_wrapper 0.2.3-beta
Ming 0.3.0
The default extensions: DOM, SPL
Tools for image creation
At this stage we now got a quite complete and nice abstraction layer, but the very simple API, limited to polygons, bitmaps and texts makes this still not really usable. In this last chapter I want to show some simple tools, to enhance this, using the existing functionality in the backends.
We will create a Tools class, which will provide static methods to create various shapes, which can then be rendered by the backends. Additionally we will provide some methods to modify existing shapes.
The polygons
For now the polygons were just defined by an array of points. This is always sufficient, but starting with this chapter we want to be able to call methods on those polygons, to modify it. For this we create a new class called Polygon, which just contains one array with the Coordinate objects spanning the polygon, and extending the ArrayObject class from SPL to offer easy access to this structure.
/**
* Simple class extending ArrayObject, containing Coordinate objects, defining
* a polygon.
*
* @version //autogen//
* @author Kore Nordmann <kore@php.net>
* @license Copyright by Kore Nordmann
*/
class Polygon extends ArrayObject
{
/**
* Array of points defining the polygon
*
* @var array(Coordinate)
*/
protected $points;
/**
* Construct Polygon from Coordniates
*
* The Polygon constructor accepts any amount of Coordinate objects, which
* define the polygon.
*
* @return void
*/
public function __construct()
{
$this->points = array();
// Add all given Coordinate objects as points to point array
$points = func_get_args();
foreach ( $points as $point )
{
// Only add Coordinate objects
if ( $point instanceof Coordinate )
{
$this->points[] = $point;
}
}
// Construct the parent constructor of the ArrayObject from the points
// array.
parent::__construct( $this->points );
}
/**
* Overloaded append method to ensure only Coordinate objects are added
*
* Overloaded append method to ensure only Coordinate objects are added
*
* @param Coordinate $value
* @return void
*/
public function append( $value )
{
if ( ! $value instanceof Coordinate )
{
throw new Exception( 'Only Coordinate objects may be addded to a polygon.' );
}
parent::append( $value );
}
/**
* Overloaded offsetSet method to ensure only Coordinate objects are added
*
* Overloaded offsetSet method to ensure only Coordinate objects are added
*
* @param Coordinate $value
* @return void
*/
public function offsetSet( $index, $newval )
{
if ( ! $newval instanceof Coordinate )
{
throw new Exception( 'Only Coordinate objects may be addded to a polygon.' );
}
parent::offsetSet( $index, $newval );
}
}
As you can see, we only overload three methods here, to fit our purpose. The constructor should accept any amount of Coordinates as parameters, so it is usable in the same way as before. Both methods, which allow to add or modify values to the ArrayObject now implement additional type checks, so that you may only add Coordinate objects, and nothing else. This is another nice side effect, we now have the possibility to ensure the type of the contents, and it is not possible any more, that we end up with some wrong values in the polygon.
Once we got this structure, we can now start implementing modification on those.
Transformation matrices
We already came in touch with transformation matrices in some of the backends, but now it is time to explain what they do, because we will implement them ourselves, to make it possible to move shapes around, rotate them, and so on.
The coordinates we got spread all over the code define points in the two dimensional coordinate vector. As described above, a coordinate is nearly equivalent to its location vector, also consisting of the same two elements. But a vector can now be multiplied with a matrix, and we get a vector as a result. For us this means, we have a coordinate (vector), multiply it with something (matrix), and get back a modified coordinate (vector). And there are several nice things about matrices, which makes this really useful for us.
You can easily specify a matrix, that will just add something to both coordinate values, which semantically means, that we just move the coordinate (translation). You may also easily specify a matrix, that rotates the coordinate by some angle around the center point of your coordinate system - same for all other imaginable transformations, like scaling, sheering, etc.
Having those single matrices, you may multiply them with each other, and you still have a matrix, which now contains all the multiplied transformations in the given order, and you can multiply a coordinate with it which now will be transformed by all these single transformations at once.
$transformation = $translation * $scaling * $rotation * $translation;
$coordinate *= $transformation;
Take a look at this short example. We create one transformation matrix out of four existing matrices, and when multiplied with the coordinate this has exactly the same effect, as the following code would have.
$coordinate *= $translation;
$coordinate *= $scaling;
$coordinate *= $rotation;
$coordinate *= $translation;
This feature of matrices does not only make your code more readable, but you can easily stack sets of transformations this way and optimize the transformations, by reducing the number of required multiplications. As you may notice, matrix multiplication is not commutative.
This is everything you need to know about matrices to use them, if you want to get some more in depth knowledge, you may want to read the wikipedia articles on this topic.
Matrix Implementation
To reduce the required knowledge about creating matrices, we will provide some useful helper methods, so you will be able to just create a rotation matrix by specifying the angle and do not need to worry about its internal structure.
You may check out the actual code from the provided code archive, but as I do not want to tell you how to create and multiply matrices here, I will just skip those code examples. For matrix and vector calculation an extension like pecl/operator, which allows operator overloading would be really useful. But as it wont ever go into the PHP core the code for creating a matrix would look like:
$matrix = Matrix::createTranslationMatrix( 10, 12 );
$matrix = $matrix->multiply(
Matrix::createScaleMatrix( 2, .5 )
);
And after you created such a stacked matrix you can modify coordinates or polygons using:
$polygon->transform( $matrix );
Since there are multiple objects which should be transformed, let's start implementing this.
Transformable
We define, that all objects in our scene, which can be transformed in some way using the given transformation matrices, should implement the interface Transformable, which is defined like:
namespace kn::Graphic;
/**
* Define interfaces
*/
interface Transformable
{
/**
* Transform using the given transformation matrix.
*
* Transform using the given transformation matrix.
*
* @param Matrix $matrix
* @return Transformable
*/
public function transform( Matrix $matrix );
}
We just require the implementation of one method, transform(), which takes a Matrix as a parameter. This method should again return a Transformable, to be able to stack the calls.
Polygon implementation
As mentioned above, the polygon class is the primary class, which should implement this interface, and the implementation looks like:
/**
* Transform polygon using the given transformation matrix.
*
* Transform polygon using the given transformation matrix.
*
* @param Matrix $matrix
* @return Polygon
*/
public function transform( Matrix $matrix )
{
// A transformation of a polygon just means, that all its coordinates
// are transformed.
foreach( $this->points as $point )
{
$point->transform( $matrix );
}
return $this;
}
If you want to transform a polygon, you just need to transform all its points, spanning the polygon. So there is nothing special here, but we got one more class, which is required to implement this interface.
Coordinate implementation
Coordinates are the atom of graphics, so here we go with some actual implementation.
/**
* Transform using the given transformation matrix.
*
* Transform using the given transformation matrix.
*
* @param Matrix $matrix
* @return Coordinate
*/
public function transform( Matrix $matrix )
{
// Vector matrix multiplication
$this->x = $this->x * $matrix->getValue( 0, 0 ) +
$this->y * $matrix->getValue( 1, 0 ) +
$matrix->getValue( 2, 0 );
$this->y = $this->x * $matrix->getValue( 0, 1 ) +
$this->y * $matrix->getValue( 1, 1 ) +
$matrix->getValue( 2, 1 );
return $this;
}
To multiply a two dimensional coordinate with a 3x3 matrix, we use to store the transformations, we need to assume a third value in the vector, which defaults to 1. You may remember from matrix multiplication, the number of columns of the first matrix must equal the number of rows in the second matrix, so that is not possible to multiply a coordinate, even with three values, with a 3x3 matrix. But as said before, we can consider a coordinate as its location vector, so that we can actually multiply them.
The implementation above just does, what is defined by the matrix definition, but reduced to the values we actually require. Consider this as a slight optimization. For a complete implementation of matrix multiplication take a look at the Matrix class.
Other implementations
I skip this here, but you may also want to let other structures we use implement Transformable, which are defined by some coordinates, like the radial gradients. This way you could also transform them, which may make sense in some applications.
Usage
So, after we made all those structures transformable, we should use this new feature in our code. The following example is only implemented using the cairo backend, but it would, of course, also work with the other backends.
$graphic = new Cairo( 150, 150 );
$graphic->addBitmap(
new Coordinate( 50, 129 ),
'bitmap.png'
);
$polygon = new Polygon(
new Coordinate( 75, 75 ), new Coordinate( 75, 10 ), new Coordinate( 85, 30 )
);
$transformation = Matrix::createRotationMatrixAroundPoint( -10, new Coordinate( 75, 75 ) );
for ( $i = 0; $i < 360; $i += 10 )
{
$graphic->drawPolygon(
$polygon,
new Color( '#2e35367f' ),
true
);
$graphic->drawPolygon(
$polygon,
new Color( '#eeeeef7f' ),
false
);
$polygon->transform( $transformation );
}
$graphic->save( 'images/example_cairo_07.png' );
We again create the canvas in the first line, and add a small background image. To create a polygon we now have to use 'new Polygon' instead of 'array', but else the API stays the same.
Beside the above mentioned convenience factories for transformation matrices, I also created a factory for a more complex matrix, which rotates a shape around any point in the graphic, besides the center point, which I use here.
With this polygon and the transformation matrix, I start a for loop, which once draws a filled black polygon, then a white border around it. After this, the polygon is transformed using the above defined matrix. With such code we can easily create beautiful fractals.
Tool class
As promised, we also want to create a tool class, which makes creating shapes easier. The static methods only need to return a Polygon object, which then can be rendered transformed etc.
namespace kn::Graphic;
/**
* Class providing static methods to create some shapes
*
* @version //autogen//
* @author Kore Nordmann <kore@php.net>
* @license Copyright by Kore Nordmann
*/
class Tools
{
// Static methods...
}
So let's start with a rectangle
Rectangle
A rectangle is somehow the simplest possible and easiest shape, but there are several possible ways to construct it. We want to construct from one of its edge points and its width and height.
/**
* Create a rectangle
*
* Create a rectangle from a position, in combination with its width and
* height.
*
* @param Coordinate $position
* @param float $width
* @param float $height
* @return Polygon
*/
public static function rectangle( Coordinate $position, $width, $height )
{
return new Polygon(
new Coordinate( $position->x, $position->y ),
new Coordinate( $position->x + $width, $position->y ),
new Coordinate( $position->x + $width, $position->y + $height ),
new Coordinate( $position->x, $position->y + $height )
);
}
With those parameters available, we can simply create the Polygon, with four edges for the four edges of the rectangle. Simple, isn't it?
Ellipse sector
Ellipses and especially ellipse sectors are an example for a far more complex shape. As said in the section about polygon size reduction, it is quite hard to reduce the size of a real ellipse sector, so that we just use a polygon to roughly interpolate the ellipse sector and our already implemented algorithm works just fine. It also may look bad if you zoom in the vector formats and detect that the outer border is not round, but consists of a lot short lines.
/**
* Create a ellipse sector
*
* Create a ellipse sector from a center point, the width and height of the
* ellipse, a start angle and end angle. You may optionally specify a
* resolion for the circle to polygon conversion.
*
* @param Coordinate $center
* @param float $width
* @param float $height
* @param float $startAngle
* @param float $endAngle
* @param int $resolution
* @return Polygon
*/
public static function circleSector( Coordinate $center, $width, $height, $startAngle, $endAngle, $resolution = 1 )
{
$polygon = new Polygon( $center );
// Convert all angles to radian values
$startAngle = deg2rad( $startAngle );
$endAngle = deg2rad( $endAngle );
$resolution = deg2rad( $resolution );
// We just need the horizontal and vertical radius
$width /= 2;
$height /= 2;
// Add points defining the ellipse
for ( $angle = $startAngle; $angle < $endAngle; $angle += $resolution )
{
$polygon->append( new Coordinate(
$center->x + sin( $angle ) * $width,
$center->x + cos( $angle ) * $height
) );
}
// Draw a last point at the end angle
$polygon->append( new Coordinate(
$center->x + sin( $endAngle ) * $width,
$center->x + cos( $endAngle ) * $height
) );
return $polygon;
}
A more complex shape, of course, also requires more complex creation code. We start the polygon with the center point of the ellipse, and then iterate over the outer border in small steps, defined by the $resolution parameter. After we added the coordinate for the last point on the outer border, we can return the completed polygon.
Usage
With this basic tool class, we now got static constructors for easy construction of more complex shapes, which can of course be used again in the code, and also be transformed, as they are only polygons.
$graphic = new Cairo( 150, 150 );
$graphic->drawPolygon( Tools::rectangle( new Coordinate( 10, 10 ), 130, 130 ), new Color( '#2e3436' ) );
$graphic->drawPolygon( Tools::circleSector( new Coordinate( 90, 90 ), 80, 30, 25, 288 ), new Color( '#f57900' ) );
$rectangle = Tools::rectangle( new Coordinate( 20, 20 ), 110, 10 );
$transformation = Matrix::createRotationMatrix( 10 );
for ( $i = 0; $i < 90; $i += 10 )
{
$graphic->drawPolygon(
$rectangle,
new Color( '#fcaf3cb0' ),
true
);
$rectangle->transform( $transformation );
}
$graphic->save( 'images/example_cairo_08.png' );
The construction of the shapes using the Tools class is quite well readable, even for the unexperienced reader. For one of the rectangles we again create a rotation matrix, so you can see that the transformations still work.
Extensions
The possible extensions of the tool class are endless, because of the unlimited amounts of available shapes. So, just add there, whatever you need.
Shape arithmetics
A very useful, but quite complicated extensions would be basic shape arithmetics, but we limited the classes to polyogns, for now, so this simplifies this a bit.
Bsaic shape arithmetics would mean, that you are able to combine or intersect polygones, which could be really useful in several cases. Another problem you'll get here is, that the diff of one polygon with another may result in "holes" in polygones, which are not possible with out current polygon structure.
But I would welcome such an extension, and implementing this could refresh your mathematical basics and your knowledge of linear algebra.
Subscribe to updates
There are multiple ways to stay updated with new posts on my blog:
Comments
Edison Lau at Sunday, 1.6. 2008
is there anyway i can get the x and y axis of an image easily?
Nabil at Tuesday, 27.1. 2009
That a very nice post, PHP image libs are very important for serious webmasters: * captchas * image copyright * ...
bayanlarla sohbet at Saturday, 18.12. 2010
This tutorial is very good. And now i can create Images like captha images with Php thank you for your attributions to us.