Image Resize Helper
I am in the process of writing an online store with Cake and needed a helper that would automatically resize images into thumbnails.
This helper resizes an image on the fly and places it in an image cache directory for later use. Make sure your imagecache directory is writable. Future editions could include a cropping function.
To use:
Download code
Helper Class:
Download code
<?php
class ImageHelper extends Helper {
var $helpers = array('Html');
var $cacheDir = 'imagecache'; // relative to IMAGES_URL path
/**
* Automatically resizes an image and returns formatted IMG tag
*
* @param string $path Path to the image file, relative to the webroot/img/ directory.
* @param integer $width Image of returned image
* @param integer $height Height of returned image
* @param boolean $aspect Maintain aspect ratio (default: true)
* @param array $htmlAttributes Array of HTML attributes.
* @param boolean $return Wheter this method should return a value or output it. This overrides AUTO_OUTPUT.
* @return mixed Either string or echos the value, depends on AUTO_OUTPUT and $return.
* @access public
*/
function resize($path, $width, $height, $aspect = true, $htmlAttributes = array(), $return = false) {
$types = array(1 => "gif", "jpeg", "png", "swf", "psd", "wbmp"); // used to determine image type
$fullpath = ROOT.DS.APP_DIR.DS.WEBROOT_DIR.DS.$this->themeWeb.IMAGES_URL;
$url = $fullpath.$path;
if (!($size = getimagesize($url)))
return; // image doesn't exist
if ($aspect) { // adjust to aspect.
if (($size[1]/$height) > ($size[0]/$width)) // $size[0]:width, [1]:height, [2]:type
$width = ceil(($size[0]/$size[1]) * $height);
else
$height = ceil($width / ($size[0]/$size[1]));
}
$relfile = $this->webroot.$this->themeWeb.IMAGES_URL.$this->cacheDir.'/'.$width.'x'.$height.'_'.basename($path); // relative file
$cachefile = $fullpath.$this->cacheDir.DS.$width.'x'.$height.'_'.basename($path); // location on server
if (file_exists($cachefile)) {
$csize = getimagesize($cachefile);
$cached = ($csize[0] == $width && $csize[1] == $height); // image is cached
if (@filemtime($cachefile) < @filemtime($url)) // check if up to date
$cached = false;
} else {
$cached = false;
}
if (!$cached) {
$resize = ($size[0] > $width || $size[1] > $height) || ($size[0] < $width || $size[1] < $height);
} else {
$resize = false;
}
if ($resize) {
$image = call_user_func('imagecreatefrom'.$types[$size[2]], $url);
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".$types[$size[2]], $temp, $cachefile);
imagedestroy ($image);
imagedestroy ($temp);
}
return $this->output(sprintf($this->Html->tags['image'], $relfile, $this->Html->parseHtmlOptions($htmlAttributes, null, '', ' ')), $return);
}
?>
To use:
Download code
echo $image->resize('myimage.jpg', 150, 150, true);
Comments
Question
1 Using it with a link
Question
2 This component rocks
Use Html::link like that:
$html->link($image->resize($image_path, WIDTH, HEIGHT, SMALL_FOTO_ASPECT), $target_link, array(), false, false);
The last used parameter (false) says if function should escape title tags.
Comment
3 thanks
Comment
4 nice
One remark though: "ROOT.DS.APP_DIR.DS.WEBROOT_DIR.DS" is a bit a lengthy description for where WWW_ROOT would be more suited :-)
I changed the $fullpath initialization to this: "$fullpath = WWW_ROOT.$this->themeWeb.IMAGES_URL;"
Also, do you think it would be possible to use the app/tmp/cache for the thumbnails?
PS: Gentoo users, don't forget to enable the "gd" use-flag for dev-lang/php if you want this to work.
Comment
5 Cache
Bug
6 I get this
70. return $this->output(sprintf($this->Html->tags['image'], $relfile, $this->Html->parseHtmlOptions($htmlAttributes, null, '', ' ')), $return);
71. }
72. ?>
there is a "}" missing at the end, you need to add it
Comment
7 deletion of function in cake 1.2
Bug
8 minor bug when image size is equal to new size
this:if ($resize) {
$image = call_user_func('imagecreatefrom'.$types[$size[2]], $url);
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".$types[$size[2]], $temp, $cachefile);
imagedestroy ($image);
imagedestroy ($temp);
}
elseif(!$cached){
$relfile = $this->webroot.$this->themeWeb.IMAGES_URL.$path;
}
This will cause helper show real image.
Comment
9 Please help
I am getting this following error.
Fatal error: Call to a member function resize() on a non-object in
Comment
10 problem with big files
There is one problem. When I upload a big file and I try to display it using the helper I get the following error:
"Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 10444 bytes) in /usr/local/psa/home/vhosts/sportschoolgekoning.nl/httpdocs/app/views/helpers/image.php on line 58"
Is there a solution to this problem?
Thanks
Bug
11 Small improvements
In the version I use I also added the functionality to render a generic image if the file isn't found but removed it in this bug report because you probably want to implement that yoursefl.
<?php
/**
* @version 1.1
* @author Josh Hundley
* @author Jorge Orpinel <jop@levogiro.net> (changes)
*/
class ImageHelper extends Helper
{
var $helpers = array('Html');
var $cacheDir = 'imagecache'; // relative to 'img'.DS
/**
* Automatically resizes an image and returns formatted IMG tag
*
* @param string $path Path to the image file, relative to the webroot/img/ directory.
* @param integer $width Image of returned image
* @param integer $height Height of returned image
* @param boolean $aspect Maintain aspect ratio (default: true)
* @param array $htmlAttributes Array of HTML attributes.
* @param boolean $return Wheter this method should return a value or output it. This overrides AUTO_OUTPUT.
* @return mixed Either string or echos the value, depends on AUTO_OUTPUT and $return.
* @access public
*/
function resize($path, $width, $height, $aspect = true, $htmlAttributes = array(), $return = false) {
$types = array(1 => "gif", "jpeg", "png", "swf", "psd", "wbmp"); // used to determine image type
if(empty($htmlAttributes['alt'])) $htmlAttributes['alt'] = 'thumb'; // Ponemos alt default
$fullpath = ROOT.DS.APP_DIR.DS.WEBROOT_DIR.DS.$this->themeWeb.'img'.DS;
$url = $fullpath.$path;
if (!($size = getimagesize($url)))
return; // image doesn't exist
if ($aspect) { // adjust to aspect.
if (($size[1]/$height) > ($size[0]/$width)) // $size[0]:width, [1]:height, [2]:type
$width = ceil(($size[0]/$size[1]) * $height);
else
$height = ceil($width / ($size[0]/$size[1]));
}
$relfile = $this->webroot.$this->themeWeb.'img'.'/'.$this->cacheDir.'/'.$width.'x'.$height.'_'.basename($path); // relative file
$cachefile = $fullpath.$this->cacheDir.DS.$width.'x'.$height.'_'.basename($path); // location on server
if (file_exists($cachefile)) {
$csize = getimagesize($cachefile);
$cached = ($csize[0] == $width && $csize[1] == $height); // image is cached
if (@filemtime($cachefile) < @filemtime($url)) // check if up to date
$cached = false;
} else {
$cached = false;
}
if (!$cached) {
$resize = ($size[0] > $width || $size[1] > $height) || ($size[0] < $width || $size[1] < $height);
} else {
$resize = false;
}
if ($resize) {
$image = call_user_func('imagecreatefrom'.$types[$size[2]], $url);
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".$types[$size[2]], $temp, $cachefile);
imagedestroy ($image);
imagedestroy ($temp);
} else {
copy($url, $cachefile);
}
return $this->output(sprintf($this->Html->tags['image'], $relfile, $this->Html->_parseAttributes($htmlAttributes, null, '', ' ')), $return);
}
}
?>
Bug
12 Bug of my changes
...} else {
copy($url, $cachefile);
...
lines for:
...} elseif (!$cached) {
copy($url, $cachefile);
...
Otherwise it will become really buggy! Sorry for that.
Comment
13 Small improvements
With Jorge Orpinel's version, which uses Html->_parseAttributes and therefore works nice with 1.2, you may have some warnings when the source image cannot be find. Just change
<?php
if (!($size = getimagesize($url)))
return; // image doesn't exist
?>
to
<?php
if (!file_exists($url) || is_dir($url) || !($size = getimagesize($url)))
return; // image doesn't exist
?>
For those who don't like using "imagecache", just change $cacheDir value: I think it may be clearer if you customize the default image cache directory using
<?php
Configure::write('Image.imagecache', 'another_directory');
?>
in config/core.php
and add these few lines to the helper:
<?php
var $cacheDir; // relative to 'img'.DS; Default to "imagecache". Defined in /config/core.php with Configure::write('Image.imagecache', 'imagecache');
function __construct()
{
$this->cacheDir = (Configure::read('Image.imagecache')?Configure::read('Image.imagecache'):'imagecache');
}
?>
This is the resulting helper:
<?php
/**
* @version 1.1
* @author Josh Hundley
* @author Jorge Orpinel <jop@levogiro.net> (changes)
* @author Arialdo Martini <arialdomartini@bebox.it> (changes)
*/
class ImageHelper extends Helper
{
var $helpers = array('Html');
//var $cacheDir = 'thumbs'; // relative to 'img'.DS
var $cacheDir; // relative to 'img'.DS; Default to "imagecache". Defined in /config/core.php with Configure::write('Image.imagecache', 'imagecache');
function __construct()
{
$this->cacheDir = (Configure::read('Image.imagecache')?Configure::read('Image.imagecache'):'imagecache');
}
/**
* Automatically resizes an image and returns formatted IMG tag
*
* @param string $path Path to the image file, relative to the webroot/img/ directory.
* @param integer $width Image of returned image
* @param integer $height Height of returned image
* @param boolean $aspect Maintain aspect ratio (default: true)
* @param array $htmlAttributes Array of HTML attributes.
* @param boolean $return Wheter this method should return a value or output it. This overrides AUTO_OUTPUT.
* @return mixed Either string or echos the value, depends on AUTO_OUTPUT and $return.
* @access public
*/
function resize($path, $width, $height, $aspect = true, $htmlAttributes = array(), $return = false) {
$types = array(1 => "gif", "jpeg", "png", "swf", "psd", "wbmp"); // used to determine image type
if(empty($htmlAttributes['alt'])) $htmlAttributes['alt'] = 'thumb'; // Ponemos alt default
$fullpath = ROOT.DS.APP_DIR.DS.WEBROOT_DIR.DS.$this->themeWeb.'img'.DS;
$url = $fullpath.$path;
if (!file_exists($url) || is_dir($url) || !($size = getimagesize($url)))
return; // image doesn't exist
if ($aspect) { // adjust to aspect.
if (($size[1]/$height) > ($size[0]/$width)) // $size[0]:width, [1]:height, [2]:type
$width = ceil(($size[0]/$size[1]) * $height);
else
$height = ceil($width / ($size[0]/$size[1]));
}
$relfile = $this->webroot.$this->themeWeb.'img'.'/'.$this->cacheDir.'/'.$width.'x'.$height.'_'.basename($path); // relative file
$cachefile = $fullpath.$this->cacheDir.DS.$width.'x'.$height.'_'.basename($path); // location on server
if (file_exists($cachefile)) {
$csize = getimagesize($cachefile);
$cached = ($csize[0] == $width && $csize[1] == $height); // image is cached
if (@filemtime($cachefile) < @filemtime($url)) // check if up to date
$cached = false;
} else {
$cached = false;
}
if (!$cached) {
$resize = ($size[0] > $width || $size[1] > $height) || ($size[0] < $width || $size[1] < $height);
} else {
$resize = false;
}
if ($resize) {
$image = call_user_func('imagecreatefrom'.$types[$size[2]], $url);
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".$types[$size[2]], $temp, $cachefile);
imagedestroy ($image);
imagedestroy ($temp);
} elseif (!$cached) {
copy($url, $cachefile);
}
return $this->output(sprintf($this->Html->tags['image'], $relfile, $this->Html->_parseAttributes($htmlAttributes, null, '', ' ')), $return);
}
}
?>
Comment
14 Image Helper like a Component.
now i need to use it in a controller.
i tried to follow this :
http://www.m3nt0r.de/blog/2007/08/12/cakephp-helpercomponent/
but it gives me some erreors :
Notice: Undefined property: ImageHelper::$themeWeb in /mydir/views/helpers/image.php on line 30
Notice: Undefined property: ImageHelper::$webroot in /mydir/views/helpers/image.php on line 43
any idea on how to correct that?
Comment
15 Same name problem
If you have two files with the same name but in different directories (eg. img/some_dir/flower.jpg and img/some_other_dir/flower.jpg), it will be treated as same file by the helper (if you attempt to resize it to the same size).
I modified the code, so that the created filename is constructed of path and filename (and not only filename).
Example: a picture in 'img/some_dir/flower.jpg' will be named '200x100_img_some_dir-flower.jpg' in the cache (if resized to 200x100).
Replace the following:
$relfile = $this->webroot.$this->themeWeb.IMAGES_URL.$this->cacheDir.'/'.$width.'x'.$height.'_'.basename($path); // relative file
$cachefile = $fullpath.$this->cacheDir.DS.$width.'x'.$height.'_'.basename($path);
with:
$dir_path = preg_replace("/[^a-z0-9_]/", "_", strtolower(dirname($path))); // make dirname suitable as a filename
$dir_path .= '-'.basename($path); // append basename
$relfile = $this->webroot.$this->themeWeb.IMAGES_URL.$this->cacheDir.'/'.$width.'x'.$height.'_'.$dir_path; // relative file
$cachefile = $fullpath.$this->cacheDir.DS.$width.'x'.$height.'_'.$dir_path; // location on server