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.
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.