Image creation with PHP - Image Tools - Kore Nordmann - PHP / Projects / Politics

Kore Nordmann - PHP / Projects / Politics

By Kore Nordmann, first published at Tue, 11 Dec 2007 11:31:06 +0100

Image creation with PHP

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.

  • Formats

    Describes the advantages and drawbacks of the different extensions and the formats they can generate.

  • Simple shapes

    Describes the basic required data structure and the generation of a first simple shape.

  • Gradients

    Describes how you can add radial and linear gradients to your generated graphics with each of the backends.

  • Integrated bitmaps

    Integrating bitmaps with the backends.

  • Text rendering

    The basics of text rendering with all extensions.

  • Additional image tools

    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.

Simple fractalSimple fractal

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' );
Rectangles and ellipseRectangles and ellipse

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.

If you liked this blog post, or learned something please consider using flattr to contribute back: .

Trackbacks

Comments

Add new comment

Fields with bold names are mandatory.

eZ Components

eZ Components

Exploring PHP

Exploring PHP

Hire me

Amazon wishlist

Powered by