Part of the EllisLab Network
x
 
Create New Page
 View Previous Changes    ( Last updated by joeles )

Category:Images -> Resize and Cache

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]