Kore Nordmann
~~~~~~~~~~~~~
:Author: Kore Nordmann
:Date: Wed, 12 Dec 2007 18:37:02 +0100
:Revision: 3
:Copyright: Copyright by Kore Nordmann
=======================
Image creation with PHP
=======================
:Copyright: Copyright by Kore Nordmann
:Description:
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. This sixth part of the article finally
covers additional tool classes which will proof helpful during image
creation.
.. contents:: Table of Contents
:depth: 3
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.
__ /blog/image_creation_with_php_formats.html
__ /blog/image_creation_with_php_shapes.html
__ /blog/image_creation_with_php_gradients.html
__ /blog/image_creation_with_php_bitmaps.html
__ /blog/image_creation_with_php_texts.html
__ /blog/image_creation_with_php_tools.html
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 archive. (134.8 KB)`__
__ image_creation_with_php_source.zip
__ image_creation_with_php_source.zip
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.
__ http://www.php.net/anoncvs.php
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
* @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`__.
__ http://en.wikipedia.org/wiki/Matrix_%28mathematics%29
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.
__ http://pecl.php.net/package/operator
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.
__ http://en.wikipedia.org/wiki/Matrix_multiplication
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.
.. image:: image_creation_with_php_src/images/example_cairo_07.png
:alt: Simple fractal
:align: right
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
* @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' );
.. image:: image_creation_with_php_src/images/example_cairo_08.png
:alt: Rectangles and ellipse
:align: right
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.
Trackbacks
==========
- Image bitmap formats on Sun, 24 May 2009 12:58:35 +0200 in Kore Nordmann - PHP / Projects / Politics
Another extract from my article series about image creation with PHP. Image
formats are often used in the wrong way. Read on to learn about them, to use
the right in the future..
- Published article "Image creation with PHP" on Sun, 24 May 2009 12:58:57 +0200 in Kore Nordmann - PHP / Projects / Politics
I just published a article series on "Image creation with PHP" as my
personal christmas present to the PHP community. This is a full guide,
covering several libraries (DOM/SVG, Cairo, Ming/Flash, GD) and all the
important topics. Read on for details...
- Image creation with PHP - Image Tools on Sun, 24 May 2009 13:03:17 +0200 in Kore Nordmann - PHP / Projects / Politics
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. This sixth part of the article finally
covers additional tool classes which will proof helpful during image
creation.
- Image creation with PHP - Text rendering on Sun, 24 May 2009 13:03:32 +0200 in Kore Nordmann - PHP / Projects / Politics
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. This fifth part of the article covers the
complex topic of text rendering with all the backends.
- Image creation with PHP - Integrating bitmaps on Sun, 24 May 2009 13:03:46 +0200 in Kore Nordmann - PHP / Projects / Politics
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. This fourth part of this article, which
describes the integration of bitmaps with the various backends.
- Image creation with PHP - Gradient rendering on Sun, 24 May 2009 13:04:00 +0200 in Kore Nordmann - PHP / Projects / Politics
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. This third part of this article makes the
images a bit more colorful, by providing support for gradients in the
graphics.
- Image creation with PHP - A first shape on Sun, 24 May 2009 13:04:14 +0200 in Kore Nordmann - PHP / Projects / Politics
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. This second part describes the generation
of shapes and the basic drawing context.
- Image creation with PHP - Formats & Libraries on Sun, 24 May 2009 13:04:30 +0200 in Kore Nordmann - PHP / Projects / Politics
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. This first part of this article describes
the various image formats and backends.
Comments
========
- Edison Lau at Sun, 01 Jun 2008 10:01:18 +0200
is there anyway i can get the x and y axis of an image easily?
- Nabil at Tue, 27 Jan 2009 03:28:29 +0100
That a very nice post, PHP image libs are very important for serious
webmasters: * captchas * image copyright * ...
- bayanlarla sohbet at Sat, 18 Dec 2010 23:26:50 +0100
This tutorial is very good. And now i can create Images like captha images
with Php thank you for your attributions to us.