CakePHP Image Resizer

I'm currently in the process of building a new CMS in CakePHP and wanted to beef up the image capabilities so that I could upload an image once and then be able to resize the image on the fly by specifying a width/height.

I found a nice plain PHP script which does the job but I wanted something that works with CakePHP so I initially went the Helper route using this I found in the bakery. Although this is great I really wanted to resize images by changing values in the url and this is what I came up with.

Requirements

Instead of using a helper to resize my images I'm going to pass all the variables through the url and direct them to a special Controller that will resize the image, save the image in a cache directory and finally output the image to the screen.

// 'width' = width of resized image
// 'height' = height of resized image
// 'resized' = set to false if no resizing is required
// 'url' = relative path to image from web root

// sample image
<img src='/images/view/width/height/resize/url' alt='Alt Tag' />

// another example
<img src='/images/view/200/100/false/dir/image.jpg' alt='Alt Tag' />

By using an image resizer this way instead of a Helper I can put this img tag inside a CMS post/article/page and the image will be automatically resized and displayed. Another advantage is that other non-technical users can upload files and resize easily.

Images Controller

The Images controller has just one public method view() which accepts a number of paramters that are not defined when the method is executed. These parameters are then parsed so that the Image can be located and then resized.

In a nutshell the method first checks the file exists, if not a special error image is displayed. If the image exists the image size and mime type are collected and then some calculations are used to create a new width and height. With these a new filename is created which will be used to create a new image. A check is made on the new filename to see if it hasn't already been created. This will save us some time resizing the image and will work as a cache.

The cache directory incidentally is declared at the top of the class and must be a writable directory on the server. If the image needs to be resized then this done depending on the mimetype of the file and finally copied to the cache directory. Because I need to display the image I need to output a new header with the image mimetype and contents of the image as read from the file.

<?php
class ImagesController extends AppController {
	var $name = 'Image';
	var $uses = array();
	
	// variables
	var $cache_dir = 'img/cache';
	var $error_img = 'img/cache/error.jpg';
	var $types = array(1 => "gif", "jpeg", "png", "swf", "psd", "wbmp");


	/**
	 * displays and resizes an image
	 * @width 	= width to resize image to
	 * @height 	= height to resize image to
	 * @resize 	= true/false
	 * @src 	= src dir of image from root
	 */
	function view() {
		// get params
		$width = $this->params['pass'][0];
		$height = $this->params['pass'][1];
		$noresize = $this->params['pass'][2];
		$url = $this->_get_url($this->params['pass']);

		// get full image path
		$full_path = WWW_ROOT.$url;

		// check file exists
		if(file_exists($full_path)) {
			// get size of image
			$size	= getimagesize($full_path);
			// get mimetype
			$mime	= $size['mime'];

			// if either width or height is an asterix
			if($width == '*' || $height == '*') {
				if($height == '*') {
					// recalculate height
					$height = ceil($width / ($size[0]/$size[1]));
				} else {
					// recalculate width
					$width = ceil(($size[0]/$size[1]) * $height);
				}
			} else {
				if (($size[1]/$height) > ($size[0]/$width)) {
					$width = ceil(($size[0]/$size[1]) * $height);
				} else {
					$height = ceil($width / ($size[0]/$size[1]));
				}
			}

			// include folder in filename
			$dir_path = preg_replace("/[^a-z0-9_]/", "_", strtolower(dirname($url)));
			$dir_path .= '-'.basename($url);
	
			// create new file names
			$file_relative = $this->cache_dir.'/'.$width.'x'.$height.'_'.$dir_path;
			$file_cached = WWW_ROOT.$this->cache_dir.DS.$width.'x'.$height.'_'.$dir_path;
	
			// if cached file already exists
			if(file_exists($file_cached)) {
				// get image sizes
				$csize = getimagesize($file_cached);
				// check that cached file is correct dimensions
				$cached = ($csize[0] == $width && $csize[1] == $height);
				// check file age
				if (@filemtime($cachefile) < @filemtime($url))
					$cached = false;
			} else {
				$cached = false;
			}

			// if file not cached
			if(!$cached) {
				$resize = ($size[0] > $width || $size[1] > $height) || ($size[0] < $width || $size[1] < $height);
			} else {
				$resize = false;
			}
	

			// do not resize if set to true
			if($noresize == 'true') {
				$resize = false;
				$cached = false;
			}
	
			// if image resize is necessary
			if($resize) {
				// image
				$image = call_user_func('imagecreatefrom'.$this->types[$size[2]], $full_path);
				if (function_exists("imagecreatetruecolor") && ($temp = imagecreatetruecolor ($width, $height))) {
					imagecopyresampled ($temp, $image, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);
				} else {
					$temp = imagecreate($width, $height);
					imagecopyresized($temp, $image, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);
				}
				call_user_func("image".$this->types[$size[2]], $temp, $file_cached);
				imagedestroy($image);
				imagedestroy($temp);
			} elseif(!$cached) {
				// copy original file
				copy($full_path, $file_cached);
			}

			// get file contents
			$data	= file_get_contents($file_cached);		
		} else {
			$size	= getimagesize($full_path);
			$mime	= $size['mime'];
			$data = file_get_contents($full_path);
		}

		// set headers and output image
		header("Content-type: $mime");
		header('Content-Length: ' . strlen($data));
		echo $data;
		exit();
	}


	/**
	 * gets the url from the parameters
	 */
	function _get_url($params) {
		// init
		$url = '';
		// unset unwanted params
		unset($params[0], $params[1], $params[2]);
		// loop through params
		foreach($params as $p) {
			$url .= $p.'/';
		}
		// remove last slash
		$url = substr($url, 0, strrpos($url, '/'));
	return $url;
	}
}
?>

Wrapping Up

This has just been a quick article with a useful bit of CakePHP functionality that I've created very recently and I wish I developed something like this on my current blog, would of made things alot easier. Any problems or suggestions then drop me an email or leave a comment.

Posted on 21st August 2008
on 21/8/08

comments powered by Disqus