Presentation of the Imagine library for manipulating images in PHP

Presentation

Imagine is an object-oriented PHP 5 library for manipulating images.
It needs to be at least version 5.3 of PHP because it uses namespaces. Originally developed (I think but I have a little doubt) for symfony 2, the library can also be used with symfony 1, or even without a framework (you only have to manage the autoload manually).

Its great interest is to simplify the manipulation of images, and above all to get rid of the various engines (GD, Imagick or Gmagick).
On the other hand, as it is compatible with these 3 engines, only the manipulations common to the 3 engines are implemented, which is Imagine's biggest flaw. But for everyday use this is quite sufficient.
As this library is full object, it is much easier to implement it in an object project or in a framework, to create typical image transformations and to reuse them.
Be careful, the library uses a system for throwing exceptions in case of a problem. Detail to take into account when using it.

The official documentation is also quite complete, you will also find the different ways to recover the library: documentation

Basic usage

When manipulating images in PHP, the steps are always substantially the same:
- we instantiate the image (either by creating it from scratch, or most often starting from a base image)
- it is subjected to various treatments (resizing or cropping, adding a watermark...)
- save the modified image in the desired format (jpg, png...)

Well with Imagine it's the same!

We start by instantiating an Imagine object by selecting the desired engine (here Gd)

$Imagine = new Imagine\Gd\Imagine();
 

Then we create an image object (here we open an existing image):

$Image = $Imagine->open($path);
 

For the different treatments on the image, Imagine considers that we apply filters, a set of filters being called a transformation.
(I'll go into more detail about filter examples later).

The recovery of the modified image can be done by writing the image to a file or by returning the source of the image for display in the browser.

# Save to file
$Image->save($new_path);
 
# Get image source
echo $Image->get($format);

When writing the image to a file, Imagine uses the extension to determine the image format (jpg, png, gif). To get the source, you have to pass the desired format as a parameter to the get() method.

Filter class creation

As the presentation indicates, Imagine filter classes must implement the following FilterInterface:interface FilterInterface

{
    /**
     * Applies scheduled transformation to ImageInterface instance
     * Returns processed ImageInterface instance
     *
     * @param Imagine\Image\ImageInterface $image
     *
     * @return Imagine\Image\ImageInterface
     */
    function apply(ImageInterface $image);
}

It contains only an apply() method that takes as parameter an object of type ImageInterface that it must return (in order to be able to chain the filters).
The ReflectionFilter example in the documentation indicates that the constructor of a filter class takes an Imagine object as a parameter.
So we can create a base filter class of this type:abstract class BaseFilter implements Imagine\Filter\FilterInterface

{
    protected $imagine;
    protected $config;
 
    public function __construct(Imagine\ImagineInterface $imagine, $conf)
    {
        $this->imagine = $imagine;
        $this->config = $conf;
    }    
 
}

I purposely added an attribute to the class compared to the example, a configuration table for the filter, in order to make the filter classes more flexible.

So here is an example of a filter to add a watermark to the bottom left of an image.
The configuration table contains the path to the watermark image (in 'source' index) and the distance in pixels in x and y of the watermark from the bottom left corner of the image:class AddWatermarkFilter extends BaseFilter implements Imagine\Filter\FilterInterface

{
    public function apply(Imagine\Image\ImageInterface $image)
    {
        // We try to create an Imagine image from the source provided in config
        // otherwise we return the starting image
        try {
            $watermark = $this->imagine
                ->open(sfConfig::get('sf_web_dir').$this->config['source']);
	} catch (Exception $e) {
	    return $image;			
	}
 
        // Calculation of the x and y points of the watermark from the size of the starting image
        // that of the watermark and the configuration	
	$pointX = $image->getSize()->getWidth() - $watermark->getSize()->getWidth() - $this->config['pointX'];
	$pointY = $image->getSize()->getHeight() - $watermark->getSize()->getHeight() - $this->config['pointY'];
 
	// We add the watermark on the original image
        $image = $image->paste($watermark, new Imagine\Image\Point($pointX, $pointY));
 
	return $image;		
    }
}

Of course, this class can be improved, for example by providing a system in case the configuration parameters are absent.
Thus, we can add these 2 methods to the base class: /**

     * @param string $key
     * @param mixed $default
     */
    protected function getConfig($key, $default = null)
    {
        return $this->hasConfig($key) ? $this->config[$key] : $default;
    }
 
    /**
     * @param string $key
     */
    protected function hasConfig($key)
    {
        return isset($this->config[$key]);
    }

The apply() method becomes: public function apply($image)

    {
        // We try to create an Imagine image from the source provided in config
        // otherwise we return the starting image
        try {
            $watermark = $this->imagine
                ->open(sfConfig::get('sf_web_dir').$this->getConfig('source'));
	} catch (Exception $e) {
	    return $image;			
	}
 
        // Calculation of the x and y points of the watermark from the size of the starting image
        // that of the watermark and the configuration	
	$pointX = $image->getSize()->getWidth() - $watermark->getSize()->getWidth() - $this->getConfig('pointX', 5);
	$pointY = $image->getSize()->getHeight() - $watermark->getSize()->getHeight() - $this->getConfig('pointY', 5);
 
	// We add the watermark on the original image
        $image = $image->paste($watermark, new Imagine\Image\Point($pointX, $pointY));
 
	return $image;		
    }

Transformation example

In this example, from a provided image, I want to create 2 images:
- a small one for a gallery
- a large one with a watermark
(note: I used Symfony 1.4 to do this code, some parts, like fetching the configuration file are framework specific)

I have a configuration file in yml that looks like this:

  images:
    formats:
      mini:
        folder: mini
        filters:
          resize:
            largeSide: 80
            smallSide: 80
      big:
        folder: big
        filters:
          resize:
            largeSide: 600
            smallSide: 400
          addWatermark:
            source: /images/imagine/watermark.png
            pointX: 10
            pointY: 10
 

And here is the code that, from the source image, creates 2 images and saves them. I purposely simplified the code and presented it in a somewhat procedural way for the article:

// Imagine initialisation
$imagine = new Imagine\Gd\Imagine();
 
// Start image instantiation
$image = $imagine->open($path);
 
// Retrieval of the config in the form of a table
// ['mini" =>['folder' => 'mini', 'filters' => []...]]
$conf = sfConfig::get('app_images_formats');
 
// For each format
foreach ($conf as $format) {
    $new_image = $image->copy();
    foreach ($format['filters'] as $filter => $config) {
        $filterClass = ucfirst($filter).'Filter';
	$filter = new $filterClass($imagine, $config);
	$new_image = $filter->apply($new_image);			
    }
    $new_image->save(sfConfig::get('app_upload_dir').'/'.$format['folder'].'/'.$name);
}
 

So we have 2 images, with the same name but saved in different folders:
- the one saved in the mini folder has a size of 80x80
- the one saved in the big folder has a size of 600x400 and has a watermark 10 pixels from the left corner of the image.

To be complete, here is the resize class:

class ResizeFilter extends BaseFilter implements Imagine\Filter\FilterInterface
{
    public function apply($image)
    {
         // Desired width and height configuration
	 if ($image->getSize()->getWidth() > $image->getSize()->getHeight()) {
	     // landscape
	     $width = $this->getConfig('largeSide', 100);
	     $heigth = $this->getConfig('smallSide', 100);
	 } else {
	     // Portrait
	     $width = $this->getConfig('smallSide', 100);
	     $heigth = $this->getConfig('largeSide', 100);
	}	
 
	// Crop if report modification
	$oldRatio = $image->getSize()->getWidth()/$image->getSize()->getHeight();
	$newRatio = $width/$heigth;
	if ($oldRatio != $newRatio) {
	    $diffWidth = $image->getSize()->getWidth() - ($image->getSize()->getHeight()*$newRatio);
	    if ($diffWidth > 0) {
	        $image= $image->crop(
                    new Imagine\Image\Point((int)($diffWidth / 2), 0),
                    new Imagine\Image\Box($image->getSize()->getHeight()*$newRatio, $image->getSize()->getHeight())
                );
	    } else {
	        $ratio = $heigth/$width;
		$diffHeight = $poImage->getSize()->getHeight() - ($image->getSize()->getWidth()*$ratio);
		$image= $image->crop(
                    new Imagine\Image\Point(0, $diffHeight / 2), 
                    new Imagine\Image\Box($image->getSize()->getWidth(), $image->getSize()->getWidth()*$ratio)
                );
	    }
	}
 
	// Resize
        $image = $image->resize(new Imagine\Image\Box($width, $heigth));
 
	return $image;
    }
}

Change image engines

It is quite simple to change the image engine used with Imagine depending on the server configuration. All you have to do is modify the instaciation of the library, the use of the design pattern factory or Dependency injection would make this task very simple.

# Use of GD
$Imagine = new Imagine\Gd\Imagine();
 
# Use of Imagick
$Imagine = new Imagine\Imagick\Imagine();
 

This permutation poses no problem because Imagine is limited to the common functions of the libraries it uses.

Conclusion

I hope this article allowed you to discover this very practical bookstore and made you want to use it. In any case, she reconciled me with image manipulation, which is really very tedious with GD.

Add a comment