sfImageTransformExtraPlugin - 1.0.3

Image manipulation made even easier!

You are currently browsing
the website for symfony 1

Visit the Symfony2 website


« Back to the Plugins Home

Signin


Forgot your password?
Create an account

Tools

Stats

advanced search
Information Readme Dependencies Releases Changelog Contribute
Show source

Image manipulation made even easier - sfImageTransformExtraPlugin

sfImageTransformExtraPlugin

Introduction

On a website you ususally find lots of images and a set of formats.

For example let's say a user avatar is always 80x80 PNG while a homepage top image is always 320x140 JPG with round corners.

As it is far too costly to prepare all these different formats by hand there are automated ways to generate them from source images uploaded by a user. One of the best tools for this in the symfony world is sfImageTransformPlugin which enables you to perform many sophisticated transformations on your images such as resizing, color manipulation, overlays and much much more. It is not only easy to use but easy to extend as well. Writing your own transformation is a bliss.

Using such an automatism means you have to write code and perform all necessary transformation - usually on upload, no matter if the generated files are ever requested. It also means that design changes that change the formats as well lead to a change of business logic rather than just templates.

This is where sfImageTransformExtraPlugin springs into action as it provides a way to configure formats with multiple transformations.

In your templates you only refer to the format by name which results in an SEO friendly image URL (a URL that you have full control over). The image itself will be generated on first request and (in production environments) written to the filesystem.

Here are some of the key features:

  • Configure image transformation for your thumbnail formats
  • Format changes without the need to change code
  • Unobstrusive implementation (No need to write code)
  • Generating images on request
  • Can be run as a web service for a content delivery network (CDN)
  • Supporting image file sources stored in databases via Doctrine or Propel, remote files and more
  • Full control over the thumbnail URLs and therefor easy to adapt to your SEO requirements
  • Generated API documentation
  • Unit tested
  • Easy to extend

A quick demonstration

Consider the following source image.

original

Here are just a few examples that were all transformed from the above image.

resize fit crop flip rotate mirror
border line rectangle arc ellipse overlay
colorize contrast edgeDetect emboss greyscale negate
opacity noise blur pixelize scatter sketchy
text rounded_corners reflection frame pattern just

And of course a simple scaling:

scale

A walk through the configuration of a more complex format

Of course you can chain transformations!

In fact most of the above thumbnails have two transformations applied one for resizing and another for the effect.

Let's see how it works and start with the original again. The format looks like this.

      star0:
        quality:                25  
        mime_type:              image/png
        transformations:        ~

With the following result.

star0

Now let's add a crop transformation to get to the correct dimensions.

      star1:
        quality:                25  
        mime_type:              image/png
        transformations:
          - { adapter: GD, transformation: crop, param: { left: 90, top: 72, width: 120, height: 120 }}

star1

And just to be a bit different we want to rotate.

      star2:
        quality:                25  
        mime_type:              image/png
        transformations:
          - { adapter: GD, transformation: crop, param: { left: 90, top: 72, width: 120, height: 120 }}
          - { adapter: GD, transformation: rotate, param: { angle: 20, background: '#FFFFFF' }}

star2

The blank spots are not what we want so let's crop it again.

      star3:
        quality:                25  
        mime_type:              image/png
        transformations:
          - { adapter: GD, transformation: crop, param: { left: 90, top: 72, width: 120, height: 120 }}
          - { adapter: GD, transformation: rotate, param: { angle: 20, background: '#FFFFFF' }}
          - { adapter: GD, transformation: crop, param: { left: 17, top: 17, width: 120, height: 120 }}

star3

So we are back to the dimensions we wanted. Now we want a watermark on top of it.

      star4:
        quality:                25  
        mime_type:              image/png
        transformations:
          - { adapter: GD, transformation: crop, param: { left: 90, top: 72, width: 120, height: 120 }}
          - { adapter: GD, transformation: rotate, param: { angle: 20, background: '#FFFFFF' }}
          - { adapter: GD, transformation: crop, param: { left: 17, top: 17, width: 120, height: 120 }}
          - { adapter: GD, transformation: overlay, param: { overlay: sfImage|overlays/logo.png, position: center }}

star4

Of course this is not "Web 2.0" enough yet..

      star5:
        quality:                25  
        mime_type:              image/png
        transformations:
          - { adapter: GD, transformation: crop, param: { left: 90, top: 72, width: 120, height: 120 }}
          - { adapter: GD, transformation: rotate, param: { angle: 20, background: '#FFFFFF' }}
          - { adapter: GD, transformation: crop, param: { left: 17, top: 17, width: 120, height: 120 }}
          - { adapter: GD, transformation: overlay, param: { overlay: sfImage|overlays/logo.png, position: center }}
          - { adapter: GD, transformation: overlay, param: { overlay: sfImage|overlays/star_frame.png, position: center }}

star5

Now we want to get rid of the bits that stick out by applying an alpha mask.

      star6:
        quality:                25  
        mime_type:              image/png
        transformations:
          - { adapter: GD, transformation: crop, param: { left: 90, top: 72, width: 120, height: 120 }}
          - { adapter: GD, transformation: rotate, param: { angle: 20, background: '#FFFFFF' }}
          - { adapter: GD, transformation: crop, param: { left: 17, top: 17, width: 120, height: 120 }}
          - { adapter: GD, transformation: overlay, param: { overlay: sfImage|overlays/logo.png, position: center }}
          - { adapter: GD, transformation: overlay, param: { overlay: sfImage|overlays/star_frame.png, position: center }}
          - { adapter: GD, transformation: alphaMask, param: { mask: sfImage|masks/star_mask.gif }}

star6

Done! ;)

Please note that for overlay and alphaMask transformations you need to prefix the resource path with "sfImage|" as this automatically converts the path into an sfImage object.

How does it work?

The generation process that we designed so far works like this:

  1. An HTTP Request is made to a thumbnail
  2. All necessary parameters are parsed from the URL
  3. The requested source image is found
  4. The thumbnail gets generated according to the format specification
  5. The thumbnail gets cached (saved to disk)
  6. The thumbnail is send out to the user

Thumbnail request lifecycle

The thumbnail image is generated on the first request. All succeeding requests are coming from cache (per default the filesystem) and are served by Apache without spawning a PHP process.

The caching mechanism

Image generation is quite expansive in terms of CPU and memory usage. This is why sfsfImageTransformExtraPlugin by default stores generated images for the production environment on the filesystem. For this the custom cache class sfRawFileCache is used which is basically a copy of sfFileCache but does not prepend the cached file with expire time information but instead saves only the generated image. It also maintains the real filename and does not add a .cache suffix.

You can see the typical lifecycle of a generated image in the following Firebug screenshots.

firebug-1-generation

The image is generated, saved to the filesystem and sent to the requesting browser.

firebug-2-static

The image is read directly from the filesystem without invoking a PHP process.

firebug-3-notmodified

Apache automatically informs the browser that the image has not been modified by sending a 304 status (depending on your Apache configuration, however the described behaviour is the default).

As you can see the time needed to deliver a generated image after the second request is drastically reduced. (The times will vary on different installations.)

The deletion of generated images use the sfCache interface and can be triggered by a task of a symfony event.

Installation

To install the plugin for a symfony project, the usual process is to use the symfony command line:

$ php symfony plugin:install sfImageTransformExtraPlugin

Alternatively, if you don't have PEAR installed, you can download the latest package attached to this plugin's wiki page and extract it under your project's plugins/ directory.

Activate the plugin in your ProjectConfiguration.class.php.

// /config/ProjectConfiguration.class.php

... $this->enablePlugins(..., 'sfImageTransformPlugin', 'sfImageTransformExtraPlugin'); ...

Enable the generating module in your settings.yml.

// /apps/yourapplication/config/settings.yml
all:
  .settings:
    enabled_modules:        [ ..., sfImageTransformator ]

...

You also need to configure automatic mime detection for sfImageTransformPlugin in your applications app.yml.

// /apps/yourapplication/config/app.yml
all:
  sfImageTransformPlugin:
    mime_type:
      auto_detect:  true
      library:    gd_mime_type #  gd_mime_type (GD), Fileinfo (PECL), MIME_Type (PEAR)
    font_dir:     %SF_PLUGINS_DIR%/sfImageTransformExtraPlugin/data/example-resources/fonts

...

Automatic mime detection is absolutely necessary! Of course you can point the font_dir to your own location containing True Type Fonts.

Clear the cache to enable the autoloading to find the new classes:

$ php symfony cc

Note: The plugin requires sfImageTransformPlugin to be installed as well. The dependencies described there apply as well so please follow the README.

The default routes of the plugin expect all thumbails to be called from the relative URL /thumbnails/... So you have to make sure, that this folder (SF_ROOT_DIR/web/thumbnails) exists and is writable to the web server.

$ mkdir SF_ROOT_DIR/web/thumbnails
$ chmod 777 SF_ROOT_DIR/web/thumbnails

The /thumbnails path is specified in your routes. If you make changes to the routes you have to make sure that the equivalent path exists and is writable.

Next you have to configure your routes.

Configuration for local image sources identified by filename

Your image sources lie in a directory accessible to your web server and you want to keep the filenames.

Create a route like the following in your applications routing.yml.

// /apps/yourapplication/config/routing.yml
sf_image:
  class: sfImageTransformRoute
  url:   /thumbnails/:format/:filepath.:sf_format
  param: { module: sfImageTransformator, action: index }
  requirements:
    format:    '[\w_-]+'
    filepath:  '[\w/]+'
    sf_format: 'gif|png|jpg'
    sf_method: [ get ]
  options:
    image_source: File

...

You can now generate <img /> tags to these images like this.

<?php
echo image_tag(url_for('sf_image_file', array('format' => 'pixelate', 'filepath' => 'logo.png')));

// resulting in: http://localhost/thumbnails/pixelate/logo.png.jpg ?>

Please note that the prefix filepath - /thumbnails in the above example - is required to exist and be writable for the webserver!

Configuration for local image sources linked by a Doctrine or Propel record

When you develop on symfony chances are that uploaded image files are located in the sf_upload_dir and their filenames are stored in the database. sfImageTransformExtraPlugin comes with two images source classes that you can use to work with these files.

Your image sources are located in a directory accessible to your web server and you want to keep the filenames.

Create a route like the following in your applications routing.yml.

// /apps/yourapplication/config/routing.yml
sf_image:
  class: sfImageTransformRoute
  url:   /thumbnails/:type/:format/:path/:slug-:id.:sf_format
  param: { module: sfImageTransformator, action: index, attribute: file }
  requirements:
    format:    '[\w_-]+'
    path:      '[\w/]+'
    slug:      '[\w_-]+'
    id:        '\d+(?:,\d+)?'
    sf_format: 'gif|png|jpg'
    sf_method: [ get ]
  options:
    image_source: Doctrine # or Propel
    segment_separators: [ '/', '.', '-' ]

...

You can now generate <img /> tags to these images like this.

<?php
echo image_tag(url_for('sf_image_doctrine', array('format' => 'pixelate', 'sf_subject' => $record)));
// resulting in: http://localhost/thumbnails/News/pixelate/01/00/00/mytest-1.jpg
?>

$record in this example is either a Doctrine or Propel record.

The :path attribute is provided by the sfImageTransformExtraPlugins own route class sfImageTransformRoute. It will take the id attribute and form a three level deep path from it.

// examples
1 => 01/00/00
37410 = 10/74/03
..

This path can ensure that you won't store more than 100 files per directory which is necessary to avoid imperformance from the filesystem.

Configuration for remote image sources to be read over HTTP

If you plan to build a content delivery network (CDN) and run sfImageTransformExtraPlugin as a webservice your image sources will not be available locally but instead over HTTP.

Your image sources are located in a directory not accessible to your web server and you want to keep the filenames.

Create a route like the following in your applications routing.yml.

// /apps/yourapplication/config/routing.yml
sf_image:
  class: sfImageTransformRoute
  url:   /thumbnails/sfweb/:format/:filepath.:sf_format
  param: { module: sfImageTransformator, action: index, protocol: http, domain: www.symfony-project.org }
  requirements:
    format:    '[\w_-]+'
    protocol:  'http|https'
    domain:    '[\w-_.]+'
    filepath:  '[\w/-_.]+'
    sf_format: 'gif|png|jpg'
    sf_method: [ get ]
  options:
    image_source: HTTP

...

You can now generate <img /> tags to these images like this.

<?php
echo image_tag(url_for('sf_image_http', array('format' => 'pixelate', 'filepath' => 'images/symfony-reloaded.png')));

// resulting in: http://localhost/thumbnails/sfweb/pixelate/images/symfony-reloaded.png.jpg ?>

Invalidating generated images

Sometimes you want to remove already generated images from your filesystem i.e. if the original source image was changed.

For this there is a symfony task which takes two mandatory arguments.

$ ./symfony help transforms:remove
Usage:
 symfony transforms:remove  application route

 Aliases: remove-thumbnails

 Arguments:
  application  The application name
  route        The sf_image route that generated the image(s) you want to remove

 Description:
  Removes thumbnails generated by sfImageTransformExtraPlugin.

Let's take a route from the previous examples.

// /apps/yourapplication/config/routing.yml
sf_image:
  class: sfImageTransformRoute
  url:   /thumbnails/:format/:filepath.:sf_format
  param: { module: sfImageTransformator, action: index }
  requirements:
    format:    '[\w_-]+'
    filepath:  '[\w/]+'
    sf_format: 'gif|png|jpg'
    sf_method: [ get ]
  options:
    image_source: File

Assuming that this route is active for your frontend application you can now do the following to remove all images generated by that route.

$ ./symfony transforms:remove frontend sf_image_file
 Do you really want to delete all generated images?
y
>> files-    Generated images removed.

To be more specific you can provide all attributes available for the route.

To remove only images for this route that used the default format:

$ ./symfony transforms:remove frontend sf_image_file --format=default
>> files-    Generated images removed.

To remove only images for this route that are of type jpg:

$ ./symfony transforms:remove frontend sf_image_file --sf_format=jpg
>> files-    Generated images removed.

Of course you can combine all available attributes. In this case to remove all jpg images that were generated using the default format (for example if you changed the mimetype of that particular format).

$ ./symfony transforms:remove frontend sf_image_file --format=default --sf_format=jpg
>> files-    Generated images removed.

Advanced usage:

How can I use static resources like fonts, alpha masks and overlay images?

To use your own Treu Type Fonts you have to configure sfImageTransformPlugin in your app.yml.

// /apps/yourapplication/config/app.yml
all:
  sfImageTransformPlugin:
    mime_type:
      auto_detect:  true
      library:    gd_mime_type #  gd_mime_type (GD), Fileinfo (PECL), MIME_Type (PEAR)
    font_dir:     /path/to/your/fonts-dir

If a transformation expects an sfImage object as a mask or overlay or other resource there is a mechanism in place forming that object for you. All you have to do is to specify the relative path to your resource image. Let's look at a previous example:

      star6:
        ...
        transformations:
          ...
          - { adapter: GD, transformation: overlay, param: { overlay: overlays/star_frame.png, position: center }}
          - { adapter: GD, transformation: alphaMask, param: { mask: masks/star_mask.gif }}

sfImageTransformManager will automatically convert any filepath/filename with one of the following extensions (jpg, jpeg, gif, png) to an instance of sfImage. The relative filepath will be looked up at the following locations: /path/to/your/project/data/resources/ or (for the default formats) /path/to/your/project/plugins/sfImageTransformExtraPlugin/data/example-resources.

As of version 1.0.2 you can also add your own resource locations in your app.yml. Look at the plugins app.yml for an example.

So for the above example the files will be looked up in the following order:

  1. /path/to/your/project/data/resources/overlays/star_frame.png /path/to/your/project/data/resources/masks/star_mask.gif

  2. /path/to/your/project/plugins/sfImageTransformExtraPlugin/data/example-resources/overlays/star_frame.png /path/to/your/project/plugins/sfImageTransformExtraPlugin/data/example-resources/masks/star_mask.gif

How can I run sfImageTransformExtraPlugin as a web service?

To run sfImageTransformExtraPlugin as a web service you create a new symfony installation and install the plugin as described in the previous chapter.

Further you will have to use your own route for remote image sources via HTTP (see above).

Remember to provide the format and all attributes required to locate the remote image source in the routes URL.

If you serve your generated images from a web service installation you have to prefix the URL with the domain of your service.

<?php echo image_tag('http://your.webservice.url'.url_for('sf_image_remote', ...), array()); ?>

How can I use a custom image source?

It doesn't matter where your source images are stored and how you need to receive the filepath/URL you can easily implement a solution by following two easy steps:

  1. Write a new route

When defining the URL of the new route the only thing you need to care about is the list of parameters. You have to provide all parameters that are necessary to retrieve the images location.

In the case of a Doctrine model storing the filename of a local file you need to know the model type (i.e. NewsBulletin), the ID of the object (i.e. 123) and the model attribute or field which stores the filename (i.e. file). Depending on your requirements you might need different parameters to identify the source.

You will also most certainly need a parameter for the desired format.

sf_image:
  class: sfImageTransformRoute
  ulr:   /thumbnails/:format/:special.sf_format
  param: { module: sfImageTransformator, action: index }
  requirements:
    format:    '[\w_-]+'
    sf_format: http|https
    somespecialparameter:    '[\w-_.]+'
    sf_method: [ get ]
  options:
    image_source: Special
  1. Write a new sfImageSource class

Once you defined the parameters you can implement the sfImageSource class by implementing sfImageSourceInterface and barfoo.

If the image source files are stored on the local filesystem accessible to the webserver your need to implement sfImageSourceLocal. For all other locations please use sfImageSourceRemote as PHP functions like stat() only work with local files and therefor need to be faked .

class sfImageSourceSpecial extends sfImageSourceRemote implements sfImageSourceInterface
{
  /**
   * Returns an sfImageSource:// URL specific to the implementing stream wrapper
   *
   * @param  array  $parameters Current request parameters
   * @return string sfImageSource:// URI
   * @throws InvalidArgumentException
   */
  public static function buildURIfromParameters(array $parameters)
  {
    ...
  }

  /**
   * Translates the given stream URL to the abolute path of the source image
   *
   * @param  string $path The given stream URL
   * @return string
   */
  private function translatePathToFilename($path)
  {
    ...
  }

}

In most cases you only have to change/adapt the methods in the above example. That is all you have to do.

Please make sure that the base URL (in the above example /thumbnails) exists and is writable.

How can I run the tests?

$ cd /path/to/sfImageTransformExtraPlugin
$ phpunit --tap test/sfImageTransformExtraPluginsTests.php

How can I generate the API documentation?

$ cd /path/to/sfImageTransformExtraPlugin
$ phpdoc -o  HTML:frames:DOM/phphtmllib -d . -t /path/to/where/you/want/the/apidocs/to/be/generated