Below is a controller I created for auto resizing and caching images on my sites. I thought others might find it useful.
I’ll add a more detailed description and demos soon. I’m sorry, my coding style is different from CI’s (Rick’s) and my comments suck.
Features:
- Images are automatically resized and cached by the path it is called by.
- Very flexible options for resizing.
- If source file is updated, the cache file(s) are automatically updated on next request.
- If browser supports IF_MODIFIED_SINCE header, image is only returned if it has been modifed since it’s last download. This saves cpu and bandwidth on server and load time for the browser.
- Only makes images smaller than their source, not larger.
- Supports images in subdirectories of the main image directory.
Installation:
NOTE: The wiki replaces “(” with ( and “)” with ). You have to do a find replace when installing. Or you could click “Edit” and copy the code from the text area. If you have problems, pm me with your email address and I’ll send you an updated zip. I’ll add a download link soon.
1) Add images.php to your controllers directory. (The name of the file and class should be the same as the images directory you want to resize/cache.)
2) Add MY_Image_lib.php to your application/libraries/ directory. This is required for fixing a crop bug in the Image Lib and adding functionality for bg color.
3) Use a .htaccess file similar to the one below. This allows sending requests for cached images to CI for processing, while still allowing regular requests for non-resized images to be served normally by Apache.
4) chmod the image directory to 777 so that Apache can write to it.
chmod 777 images
Usage:
Call the image via an <img> tag and add the desired size in the url as below. If your source image is /images/image.gif:
1) <img src=”/images/size/200x200/image.gif” /> - Produces an image resized and then cropped to and 200x200 square. Useful for square thumbnails. Width must equal height to trigger cropping. Otherwise, image is resized as described in #2.
2) <img src=”/images/size/200x300/image.gif” /> - Produces an image resized to so that width does not exceed 300 and height does not exceed 200.
3) <img src=”/images/size/200/image.gif” /> - Produces an image with the longest side sized to 200, the shorter side is auto-sized proportionally.
4) <img src=”/images/size/200x/image.gif” /> - Produces an image with the width sized to 200, the height is auto-sized proportionally.
5) <img src=”/images/size/x200/image.gif” /> - Produces an image with the height sized to 200, the width is auto-sized proportionally.
application/controllers/images.php
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Images extends Controller {
function Images() {
parent::Controller();
// directory where source images are stored
$this->img_dir = $_SERVER['DOCUMENT_ROOT'] . site_url('/images');
// directory where cached files will be stored.
$this->cache_dir = $this->img_dir . '/.cached';
// image library to use, GD, GD2,
$this->img_lib = 'GD2';
// image quality
$this->quality = 75;
// bg color if src file has transparent bg, gd defaults to black which is not usually pretty
$this->background = array(255, 255, 255);
// max memory limit, used when resizing/cropping to handle large files
$this->memory_limit = '64M';
// END OF CONFIGURATION --------------------------------------- //
}
function size() {
// only function callable externally
// offset used determining image. not hard coded because ci could be in a sub dir
$this->offset = array_search(__FUNCTION__, $this->uri->segment_array())+1;
$file = $this->_get_file_from_uri();
$src_file = $this->img_dir . $file;
$dst_size = $this->uri->segment($this->offset);
$dst_file = $this->cache_dir . '/' . $dst_size . $file;
if (!is_file($src_file)) show_404();
if (is_file($dst_file) && (filemtime($src_file) > filemtime($dst_file))) {
// if src file is newer than the cache file, delete cache
unlink($dst_file)
clearstatcache();
}
$config = array(
'new_image' => $dst_file,
'quality' => $this->quality,
'source_image' => $src_file
);
if ($this->background != FALSE) $config['background'] = $this->background;
list($src['width'], $src['height']) = getimagesize($src_file);
if ($dst_size == 'x') {
// no size given so 404
show_404();
} else if (strpos($dst_size, 'x') !== FALSE) {
// create cache image to fit $dst_size
list($dst['width'], $dst['height']) = explode('x', $dst_size);
if (($dst['width'] == $dst['height']) && !file_exists($dst_file)) {
// image isn't aready cached, crop the image to square
$crop = $src;
$crop['new_image'] = dirname($config['new_image']) . '/_' . basename($config['new_image']);
if ($src['width'] < $src['height']) {
$crop['y_axis'] = round(($src['height'] - $src['width']) / 2);
$crop['height'] = $src['width'];
// if y_axis > height, this is a really tall image, so the focus is probably toward the top, adjust the y_axis
while ($crop['y_axis'] > $crop['height']) $crop['y_axis'] /= 2;
$crop['library_path'] = '/usr/bin/';
} else {
$crop['x_axis'] = round(($src['width'] - $src['height']) / 2);
$crop['width'] = $src['height'];
}
$crop = array_merge($config, $crop);
$this->_crop_img($crop);
$dst = array('source_image'=>$crop['new_image'], 'width'=>$dst_size, 'height'=>$dst_size);
} else {
// calculate width, height
if (empty($dst['height'])) $dst['height'] = floor($src['height']*($dst['width']/$src['width']));
if (empty($dst['width'])) $dst['width'] = floor($src['width']*($dst['height']/$src['height']));
}
} else $dst = array('width'=>$dst_size, 'height'=>$dst_size);
// check that the resized image won't be larger than the original
if (($src['width'] < $dst['width']) && ($src['height'] < $dst['height'])) $dst = $src;
$config = array_merge($config, $dst);
$this->_serve_cached_img($config);
}
function _crop_img($config=array()) {
// perform the resize as specified in config
// increase memory limit to handle large files
ini_set('memory_limit', $this->memory_limit);
$this->load->library('image_lib');
$default = array(
'image_library' => $this->img_lib,
'maintain_ratio' => FALSE
);
$config = array_merge($default, $config);
$this->image_lib->initialize($config);
$this->_mk_dir(dirname($config['new_image']));
$result = $this->image_lib->crop();
if (!$result || !file_exists($config['new_image'])) echo 'Crop: '.$this->image_lib->display_errors();
}
function _get_ext($file) {
// returns file's extension
return substr($file, strrpos($file, '.')+1);
}
function _get_file_from_uri() {
// returns the requested image from the uri
$seg_array = $this->uri->segment_array();
return '/' . implode('/', array_slice($seg_array, $this->offset));
}
function _get_mime($file) {
// returns mime-type based on file extension, hard-coded for now
$mimes = array('bmp'=>'image/bmp', 'gif'=>'image/gif', 'jpg'=>'image/jpeg', 'png'=>'image/png');
return $mimes[$this->_get_ext($file)];
}
function _mk_dir($path) {
// recursively creates directories in path if they don't exist
if (is_dir($path)) return TRUE;
if (!$this->_mk_dir(dirname($path), 0777)) return FALSE;
$old_umask = umask(0);
$result = mkdir($path, 0777);
umask($old_umask);
return $result;
}
function _resize_img($config=array()) {
// perform the resize as specified in config
// increase memory limit to handle large files
ini_set('memory_limit', $this->memory_limit);
$this->load->library('image_lib');
$default = array(
'background' => array(255, 255, 255),
'image_library' => $this->img_lib,
'maintain_ratio' => TRUE
);
$config = array_merge($default, $config);
$this->image_lib->initialize($config);
$this->_mk_dir(dirname($config['new_image']));
$result = $this->image_lib->resize();
if (!$result) echo $this->image_lib->display_errors();
}
function _serve_cached_img($config) {
// creates and serves cached images
$src_file = $config['source_image'];
$cache_file = $config['new_image'];
if (!file_exists($cache_file)) {
// cache file doesn't exist, create it
$this->_resize_img($config);
// if temp crop file, delete
if ($src_file == dirname($cache_file).'/_'.basename($cache_file)) unlink($src_file)
} else {
// else check modified date
$request = apache_request_headers();
if (isset($request['If-Modified-Since'])) {
$modified_since = explode(';', $request['If-Modified-Since']);
$modified_since = strtotime($modified_since[0]);
} else $modified_since = 0;
if (filemtime($cache_file) <= $modified_since) {
// if not modified, save some cpu and bandwidth
header('HTTP/1.1 304 Not Modified');
header('Etag: '.md5($cache_file));
die;
}
}
// serve cache file
$mime = $this->_get_mime($cache_file);
header('Last-Modified: ' . gmdate('D, d M Y H:i:s T', filemtime($cache_file)));
header('Content-Type: '.$mime);
header('Content-Length: '.filesize($cache_file)."\n\n");
header('Etag: '.md5($cache_file));
readfile($cache_file)
die;
}
}
?>
application/libraries/MY_Image_lib.php
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class MY_Image_lib extends CI_Image_lib {
var $background = array();
function image_process_gd($action = 'resize')
{
$v2_override = FALSE;
if ($action == 'crop')
{
// If the target width/height match the source then it's pointless to crop, right?
// EDITED, fixed crop "short-circuit" bug
if ($this->width >= $this->orig_width AND $this->height >= $this->orig_height)
// /EDITED
{
// We'll return true so the user thinks the process succeeded.
// It'll be our little secret...
return TRUE;
}
// Reassign the source width/height if cropping
$this->orig_width = $this->width;
$this->orig_height = $this->height;
// GD 2.0 has a cropping bug so we'll test for it
if ($this->gd_version() !== FALSE)
{
$gd_version = str_replace('0', '', $this->gd_version());
$v2_override = ($gd_version == 2) ? TRUE : FALSE;
}
}
else
{
// If the target width/height match the source, AND if
// the new file name is not equal to the old file name
// we'll simply make a copy of the original with the new name
if (($this->orig_width == $this->width AND $this->orig_height == $this->height) AND ($this->source_image != $this->dest_image))
{
if ( ! @copy($this->full_src_path, $this->full_dst_path))
{
$this->set_error('imglib_copy_failed');
return FALSE;
}
@chmod($this->full_dst_path, 0777);
return TRUE;
}
// If resizing the x/y axis must be zero
$this->x_axis = 0;
$this->y_axis = 0;
}
// Create the image handle
if ( ! ($src_img = $this->image_create_gd()))
{
return FALSE;
}
// Create The Image
if ($this->image_library == 'gd2' AND function_exists('imagecreatetruecolor') AND $v2_override == FALSE)
{
$create = 'imagecreatetruecolor';
$copy = 'imagecopyresampled';
}
else
{
$create = 'imagecreate';
$copy = 'imagecopyresized';
}
$dst_img = $create($this->width, $this->height);
// EDITED, added support for background color
if (!empty($this->background)) {
list($r,$g,$b) = $this->background;
$fill = imagecolorallocate($dst_img, $r, $g, $b);
imagefill($dst_img, 0, 0, $fill);
}
// /EDITED
$copy($dst_img, $src_img, 0, 0, $this->x_axis, $this->y_axis, $this->width, $this->height, $this->orig_width, $this->orig_height);
// Show the image
if ($this->dynamic_output == TRUE)
{
$this->image_display_gd($dst_img);
}
else
{
// Or save it
if ( ! $this->image_save_gd($dst_img))
{
return FALSE;
}
}
// Kill the file handles
imagedestroy($dst_img);
imagedestroy($src_img);
// Set the file to 777
@chmod($this->full_dst_path, 0777);
return TRUE;
}
}
?>
.htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ ./index.php/$1 [L]
