CakePHP Image Resizer

21st August 2008 CakePHP

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.

Back to Home Page

Comments

James (15/09/2008 - 04:50)

@Nd: hmm very good point, I suppose you could write in a small check to combat this and also include a check to see that the image is on the same domain as your site to stop external images being saved. Thanks for commenting.

Add Your Comment

Recently Watched Films

Mr Brooks Rec Pathology Diary of the Dead

TV Shows

The Shield Flight of the Conchords

Site by James Fairhurst 2008, all rights reserved and all that malarky