Media asset management: resize images

In a previous article I talked about a method to resize images on demand. The article (from October 2009) was written with extensibility in mind, but (of course) not with the counterparts we found out after a while. On-demand resizing of images server-side is very nice, because:

  1. Browsers are less capable to resize images
  2. Bandwidth is minimalized for unnecessary large images

Nevertheless we ran into two drawbacks with this method:

  1. Images are rendered on each request
  2. Every width or height is possible to resize

The first one is bad because it has a huge impact on performance. Every render costs processing time, so images should actually be cached. Preferably this should be done without interference of php processing: when a resized version is already available, Apache should be able to serve this file directly without required work of php.

The second drawback is more a silent attack: you don’t notice the problem until you get pointed to it yourself (or get attacked with this method). When you cache the resized versions (but you allow all different widths and height) it is still easy to program an attack. A computer will request an image with a width of 100, 101, 102, 103 etc. Every request, the image is different and therefore the server has to resize the image to the right size over and over again.

Therefore, we now use an image process method which includes the following benefits:

  1. Server-side resize of images
  2. Images are cached after resize
  3. Only a few variations are possible
  4. All variations are grouped into one directory

The last point is also very useful, because when you update the original image, you’d like to remove all old variations of the old original at once. With the hassle of spreaded variations of images, you never get this done properly.

The idea

The idea is to have variations of images using a special key. For example, the key “small” means a (maximum) width of 100px. And the key “square” means a variation which is cropped with a ratio of 1:1 and a height of 250px. The variations are related to the original, because the variations are stored in a directory together and linked to the orignal due to the name of the directory.

An example: you have my-image.png as original. The original is located at mydomain.tld/img/my-image.png. A variation is already created with the key “small”. A directory is created .my-image.png (mind the dot!) in the same folder. In this folder, the variation is stored under the filename small.png. The path to this variation is therefore mydomain.tld/img/.my- image.png/small.png.

With only a small set of pre-defined keys, you are sure nobody requests images of sizes you never imagined. The control gives enough freedom for yourself, but constrains other users.

A better uri for variations

Of course, the filesystem organisation of images is uncommon in an uri. The proposed uri scheme is to have the name of the variation just before the extension. In the example of above, the small variation would be accessible through the uri mydomain.tld/img/my-original.small.png. This is actually the method I prefer myself when I work with files on my local filesystem.

The .htaccess helps to convert this request uri into the right path on the filesystem. Step 1 is to check whether this file exists (a condition). Step 2 is to point the request to the right location.

RewriteCond %{DOCUMENT_ROOT}img$1/.$2.$4/$3.$4 -s
RewriteRule ^img(.*)/([a-zA-Z0-9-_\.]*)\.([^/]+)\.([a-z]{2,4})$ img$1/.$2.$4/$3.$4 [NC,L,QSA]

The regular expressions look horrible, but the first part is only to make sure the rule works for files in the img directory and for files inside subdirectories (the (.*) part).

Next, the bigger regular expression extracts the filename of the image (this case my-image), the key of the variation (small) and the extension (png). It is even possible to have an original filename with dots, so my-image.v1.png will also work when you request my-image.v1.small.png! Of course, alphanumeric characters, dashes and underscores are allowed too.

Resize non-existing variations

The last part for the .htaccess is to pass non-existing variations to a php script. The script needs a few parameters: the directory of the original, the filename of the original, the key of the variation and the extension of the image.

Just below the RewriteCond and RewriteRule, this rule passes non-existing variations to a php script:

RewriteRule ^img(.*)/([a-zA-Z0-9-_\.]*)\.([^/]+)\.([a-z]{2,4})$ img/image.php?path=$1&file=$2.$4&key=$3&ext=$4 [NC,L,QSA]

Inside the php script, you are able to create the paths to original and the resized image:

$root = dirname(__FILE__);
$path = $_GET['path'];
$file = $_GET['file'];
$key  = $_GET['key'];
$ext  = $_GET['ext'];

$original  = $root . $path . DIRECTORY_SEPARATOR . $file;
$cache     = $root . $path . DIRECTORY_SEPARATOR . '.' . $file;
$variation = $cache . DIRECTORY_SEPARATOR . $key . '.' . $ext;

The last step is to connect somehow the resize/crop parameters to the key of the variation. We did this with an ini file:

small.resize.maxLongest = "100"

square.resize.maxWdith = "200"
square.crop.aspectRatio = "1:1"

In the ini file, the first term is the key for the variation. Next, the process step is stated (resize or crop). Then the process parameters are put in the end with a specific value. In this case maxLongest means any side (width or height) should not be longer than 100 pixels. The 1:1 aspect ratio means the crop will be a square.

The “real” work

With all preparations, php is now able to create a new image. Based on the original, the key of a variation and the ini file, is is pretty straightforward to create a script with the necessary checks and image manipulating actions. I assume from this point you are able to write the code yourself. If not, leave a comment so I can explain a bit more of this last part of the process.