phpThumb Component

By Nate Constant (Natcon67)
This is my first attempt at writing a component for CakePHP. If you have any suggestions please add them to the comments and I'll incorporate them.
download the latest copy of phpThumb from http://phpthumb.sourceforge.net and extract it into your vendors directory. Should end up like: /vendors/phpThumb/{files}.

Add this file into your components directory (/app/components/thumb.php).

Component Class:

Download code <?php 
/**
 * Thumbnail Generator for CakePHP that uses phpThumb (http://phpthumb.sourceforge.net/)
 *
 * @package default
 * @author Nate Constant
 **/ 

class ThumbComponent{
    
    
/**
     * The mime types that are allowed for images
     */
    
var $allowed_mime_types = array('image/jpeg','image/pjpeg','image/gif','image/png');
    
    
/**
     * File system location to save thumbnail to.  ** Must be writable by webserver 
     */
    
var $image_location 'images';
    
    
/**
     * Array of errors
     */
    
var $errors = array();
    
    
/**
     * Default width if not set
     */
    
var $width 100;
    
    
/**
     * Default height if not set
     */
    
var $height 100;
    
    
/**
     * do we zoom crop the image?
     */
    
var $zoom_crop 0;//do not zoom crop
    
    /**
     * The original image uploaded
     * @access private
     */
    
var $file;
    
    var 
$controller;
    var 
$model;
    
    function 
startup( &$controller ) {
      
$this->controller = &$controller;
    }
    
    
/**
     * This is the method that actually does the thumbnail generation by setting up 
     * the parameters and calling phpThumb
     *
     * @return bool Success?
     * @author Nate Constant
     **/
    
function generateThumb($filename,$model){
        
// Make sure we have the name of the uploaded file and that the Model is specified
        
if(empty($filename) || !$this->controller->data[$model][$filename]){
            
$this->addError('non-existant file'.$filename);
            return 
false;
        }
        
// save the file to the object
        
$this->file $this->controller->data[$model][$filename];
        
        
// verify that the size is greater than 0 ( emtpy file uploaded )
        
if(!$this->file['size']){
            
$this->addError('File Size is 0');
            return 
false;
        }
        
        
// verify that our file is one of the valid mime types
        
if(!in_array($this->file['type'],$this->allowed_mime_types)){
            
$this->addError('Invalid File type: '.$this->file['type']);
            return 
false;
        }
        
// verify that the filesystem is writable, if not add an error to the object
        // dont fail if not and let phpThumb try anyway
        
if(!is_writable(WWW_ROOT.DS.$this->image_location)){
            
$this->addError('directory: '.WWW_ROOT.DS.$this->image_location.' is not writable.');
        }
        
        
// Load phpThumb
        
vendor('phpThumb'.DS.'phpthumb.class');
        
$phpThumb = new phpThumb();
        
$phpThumb->setSourceFilename($this->file['tmp_name']);
        
$phpThumb->setParameter('w',$this->width);
        
$phpThumb->setParameter('h',$this->height);
        
$phpThumb->setParameter('zc',$this->zoom_crop);
        if(
$phpThumb->generateThumbnail()){
            if(!
$phpThumb->RenderToFile(WWW_ROOT.DS.$this->image_location.DS.$this->file['name'])){
                
$this->addError('Could not render file to: '.$this->image_location.DS.$this->file['name']);
            }
        } else {
            
$this->addError('could not generate thumbnail');
        }
        
        
// if we have any errors, remove any thumbnail that was generated and return false
        
if(count($this->errors)>0){
            if(
file_exists(WWW_ROOT.DS.$this->image_location.DS.$this->file['name'])){
                
unlink(WWW_ROOT.DS.$this->image_location.DS.$this->file['name']);
            }
            return 
false;
        } else return 
true;
            
    }
    
    function 
addError($msg){
        
$this->errors[] = $msg;
    }
    
}
?>

And here's an example usage in your controller.

Controller Class:

Download code <?php 
class ImagesController extends AppController
{
    var 
$name 'Images';
    var 
$components = array('Thumb');
    var 
$uses = array('Image');

    
    function 
add(){
        if(!empty(
$this->data)){
            if(
$this->data['Image']['thumbnail']['size']){
                
pr($this->data);
                if(!
$this->Thumb->generateThumb('thumbnail','Image')){
                    
pr($this->Thumb->errors);
                }            
            }
        }
    }
    
}
?>

 

Comments 274

CakePHP Team Comments Author Comments
 

Comment

1 Helper

Nice - this will be equally helpful as a helper for views. The resizing can be thus deferred till the first viewing.
Posted Mar 21, 2007 by Dr. Tarique Sani
 

Comment

2 Not able to run it

Hi,

Thanks for the tutorial, but I am not able to run it.

I setup everything you detailed in your example.

However, it always says, unable to create thumb.

I know I don't have ImageMagick, but by definition of phpThumb, it should use GD library to create thumbs, but unfortunately, it's not.

When I go to the phpThumb/demos/phpThumb.demo.demo.php, I can see thumbnails getting created, which means that their code works, only there is a flag somewhere to use gd and not imagemagick?

Do you know of it?

-Mandy.
Posted Mar 27, 2007 by Mandy Singh
 

Comment

3 imagemagick vs GD

Mandy - from the phpthumb Sourceforge page intro.:
". ImageMagick is used wherever possible for speed. "

so I think you need to check in phpThumb somewhere :)
Posted Mar 27, 2007 by Luke Barker
 

Comment

4 extra debugging help

If you're getting errors and having problems with the component try adding a little debugging into it by doing this.

In the thumb.php component at line 99 add this code below the existing $this->addError() call:

$this->addError(implode('; ',$phpThumb->debugmessages));

This will tell you everything that phpThumb is doing and will help you track down the problem better.

Please, if you find common errors that I can add to the component that will give a better user experience, please add them in the comments here.
Posted Mar 27, 2007 by Nate Constant
 

Comment

5 Rewrite

Drayen, very nice job! Thanks for modifying my original code; yours has many more features and is written very well. I'd be happy to update this current component or link to a new component that you create. Let me know.

Nate
Posted Apr 3, 2007 by Nate Constant
 

Question

6 modified component

Drayen, very nice job!
hey there, this is exactly what i need in my current project. has Drayen's code been published somewhere? thank you both for your work! :)
leo.
Posted Apr 12, 2007 by Leonhard Melzer
 

Comment

7 My code

Drayen, very nice job!
hey there, this is exactly what i need in my current project. has Drayen's code been published somewhere? thank you both for your work! :)
leo.

Hi Leo, there was talk of updating this code with mine, but both Nate and i seem to be very busy. I tried to publish my code but got rejected because this post exists ?!?! Here it is in the comments instead :)

Component Class:

<?php 
<?php
/* $Id$ */
/**
 * PhpThumbComponent - A CakePHP Component to use with PhpThumb
 * Copyright (C) 2007-2008 Alex McFadyen aka Drayen
 *
 * @license MIT
 */

/**
 * PhpThumbComponent - A CakePHP Component to use with PhpThumb 
 * (http://phpthumb.sourceforge.net/)
 * 
 * Based on CakePHP tutorial on : http://bakery.cakephp.org/articles/view/274
 * 
 * This component will allow you to create/display thumbnails as well as have them
 * auto generated and cached.
 * 
 * Its set up to allow off site linking.
 * 
 * @package PhpThumbComponent
 * @subpackage controllers.components
 * 
 * @author Alex McFadyen aka Drayen
 * 
 * @version 0.1
 **/

class PhpThumbComponent extends Component{

    
/**
     * Array of errors
     */
    
var $errors = array();
    
    
    
/**
     * The mime types that are allowed for images
     */
    
var $allowed_mime_types = array(IMAGETYPE_JPEG,IMAGETYPE_GIF,IMAGETYPE_PNG);

    
/**
     * File system location to save thumbnails to.  
     */
    
var $cache_location null;

    
/**
     * How old the cached image can be before its updated.
     */
    
var $max_cache_age 7776000#90 days in seconds
    
    /**
     * Size in bytes that the cashe folder can reach before it over writes old images,
     * comment out to disable
     */
    
var $max_cache_size 10485760//10 * 1024 * 1024;
    
    /**
     * Default width if not set
     */
    
var $width 100;

    
/**
     * Default height if not set
     */
    
var $height 100;
    
    
/**
     * Jpeg quality
     */
    
var $quality 100;

    
/**
     * do we zoom crop the image?
     */
    
var $zoom_crop 0;//do not zoom crop

    /**
     * keep a fixed aspect ratio?
     */
    
var $fixed_aspect_ratio 1;//do not streach

    /*
     * When not zoom cropping, diffent aspect ratios causes space
     * this is the fill colour
     */
    
var $background 'FFFFFF';
    
    
    var 
$controller;
    var 
$model;

    function 
startup( &$controller ) {
        
$this->controller = &$controller;
        
$this->cache_location CACHE.'thumbs'.DS;
    }


    
    
/**
     * Will generate a thumbnail as defined by the presets (or by $_GET vars)
     * and place it in the target. If display = true it will also output the 
     * thumbnail.
     * 
     * @param string $source the location of the source image (may be relative or absolute)
     * @param string $target the target directory and filename for the generated thumbnail
     * @param bool $overwrite if the target should be overwritten
     * @param bool $display if the image should be displayed
     * @return bool Success?
     * @author Alex McFadyen
     */
    
function generateThumbnail($source null$target null$overwrite true$display false){

        
$target_dir substr($target0, -(strpos(strrev($target),'/')));

        if(
$source == null OR $target == null){//check correct params are set
            
$this->addError("Both source[$source] and target[$target] must be set");
            return 
false;/*
        }elseif(!is_file($source)){//check source is a file
            $this->addError("Source[$source] is not a valid file");
            return false;*/
        
}elseif(in_array($this->ImageTypeToMIMEtype($source), $this->allowed_mime_types)){//and is of allowed type
            
$this->addError("Source[$source] is not a valid file type");
            return 
false;
        }elseif(!
is_writable($target_dir)){//check if target directory is writeable
            
$this->addError("Can not write to target directory [$target_dir]");
            return 
false;
        }elseif(
is_file($target) AND !$overwrite){//check if target is a file already and not ok to be over written
            
$this->addError("Target[$target] exsists and overwrite is not true");
            return 
false;
        }elseif(
is_file($target) AND !is_writable($target)){
            
$this->addError("Can not overwrite Target[$target]");
            return 
false;
        }

        
//load PhpThumb
        
vendor('phpThumb'.DS.'phpthumb.class');
        
$phpThumb = new phpThumb();
        
        
//set presets
        
$phpThumb->config_nohotlink_enabled false;
        
$phpThumb->config_nooffsitelink_enabled false;
        
$phpThumb->config_prefer_imagemagick true;
        
$phpThumb->config_output_format 'jpeg';
        
$phpThumb->config_error_die_on_error true;
        
$phpThumb->config_allow_src_above_docroot true;
        
        
        
//optionals 
        
if(isset($this->max_cache_size)) $phpThumb->config_cache_maxsize $this->max_cache_size;
        
        
//load in source image
        
$phpThumb->setSourceFilename($source);
        
        
//load vars from $_GET if they are set
        //width
        
if(!isset($_GET['w']))
            
$phpThumb->setParameter('w'$this->width);
        else
            
$phpThumb->setParameter('w'$_GET['w']);
        
        
//height
        
if(!isset($_GET['h']))
            
$phpThumb->setParameter('h'$this->height);
        else
            
$phpThumb->setParameter('h'$_GET['h']);
        
        
//zoom crop
        
if(!isset($_GET['zc']))
            
$phpThumb->setParameter('zc'$this->zoom_crop);
        else
            
$phpThumb->setParameter('zc'$_GET['zc']);
            
        
//Fixed Aspect Ratio
        
if(!isset($_GET['far']))
            
$phpThumb->setParameter('far'$this->fixed_aspect_ratio);
        else
            
$phpThumb->setParameter('far'$_GET['far']);
                
        
// background
        
if(!isset($_GET['bg']))
            
$phpThumb->setParameter('bg',  $this->background);
        else
            
$phpThumb->setParameter('bg'$_GET['bg']);
            
        
//quality
        
if(!isset($_GET['q']))
            
$phpThumb->setParameter('q'$this->quality);
        else
            
$phpThumb->setParameter('q'$_GET['q']);
            
        
//create the thumbnail
        
if($phpThumb->generateThumbnail()){
            if(!
$phpThumb->RenderToFile($target)){
                
$this->addError('Could not render file to: '.$target);
            }elseif(
$display==true){
                
$phpThumb->OutputThumbnail();    
                die();
//not perfect, i know but it insures cake doenst add extra code after the image.
            
}
        } else {
            
$this->addError('could not generate thumbnail');
        }

        
// if we have any errors, remove any thumbnail that was generated and return false
        
if(count($this->errors)>0){
            if(
file_exists($target)){
                
unlink($target);
            }
            return 
false;
        } else return 
true;
    }
    
    
    
    
/**
     * Display and/or generate a auto-named thumbnail, based on presets in $_GET.
     *
     * @param string $source the location of the source image (may be relative or absolute)
     * @param bool $forceUpdate if the thumbnal should be refreashed
     * @param bool $display if the image should be displayed
     * @return bool Success?
     * @author Alex McFadyen
     */
    
function displayThumbnail($source$forceUpdate false$display true){
        if(
$source == null$source $_GET['src'];

        
$cache_filename $this->cache_location md5(env('REQUEST_URI')) . '_' md5($source).'.jpg';
    
        
#check the cache'ed image exsists and its new enough and that it needs to be displayed
        
if(is_file($cache_filename//file exsists
                
AND (time() < filectime($cache_filename) + $this->max_cache_age//not too old
                
AND (is_file($source) ? ( filectime($cache_filename) > filectime($source) ) : true//cached image is newer than source
                
AND ($display == true
                AND !(
$forceUpdate == true)) 
            {

            
header('Content-Type: '.IMAGETYPE_JPEG);
            @
readfile($cache_filename);

            exit();
//not perfect, i know but it insures cake doenst add extra code after the image.
        
}else{
            return 
$this->generateThumbnail($source$cache_filenametrue$display);
        }
    }
    
    
/**
     * Function borrowed form phpThumb libs
     */
    
function ImageTypeToMIMEtype($imagetype) {
        if (
function_exists('image_type_to_mime_type') && ($imagetype >= 1) && ($imagetype <= 16)) {
            
// PHP v4.3.0+
            
return image_type_to_mime_type($imagetype);
        }
        static 
$image_type_to_mime_type = array(
            
1  => 'image/gif',                     // IMAGETYPE_GIF
            
2  => 'image/jpeg',                    // IMAGETYPE_JPEG
            
3  => 'image/png',                     // IMAGETYPE_PNG
            
4  => 'application/x-shockwave-flash'// IMAGETYPE_SWF
            
5  => 'image/psd',                     // IMAGETYPE_PSD
            
6  => 'image/bmp',                     // IMAGETYPE_BMP
            
7  => 'image/tiff',                    // IMAGETYPE_TIFF_II (intel byte order)
            
8  => 'image/tiff',                    // IMAGETYPE_TIFF_MM (motorola byte order)
            
9  => 'application/octet-stream',      // IMAGETYPE_JPC
            
10 => 'image/jp2',                     // IMAGETYPE_JP2
            
11 => 'application/octet-stream',      // IMAGETYPE_JPX
            
12 => 'application/octet-stream',      // IMAGETYPE_JB2
            
13 => 'application/x-shockwave-flash'// IMAGETYPE_SWC
            
14 => 'image/iff',                     // IMAGETYPE_IFF
            
15 => 'image/vnd.wap.wbmp',            // IMAGETYPE_WBMP
            
16 => 'image/xbm',                     // IMAGETYPE_XBM
    
            
'gif'  => 'image/gif',                 // IMAGETYPE_GIF
            
'jpg'  => 'image/jpeg',                // IMAGETYPE_JPEG
            
'jpeg' => 'image/jpeg',                // IMAGETYPE_JPEG
            
'png'  => 'image/png',                 // IMAGETYPE_PNG
            
'bmp'  => 'image/bmp',                 // IMAGETYPE_BMP
            
'ico'  => 'image/x-icon',
        );

        return (isset(
$image_type_to_mime_type[$imagetype]) ? $image_type_to_mime_type[$imagetype] : false);
    }
    
    
/**
     * Clears the current set cache directory of expired files (or all) images
     *
     * @param bool $clearAll if set, it will clear all the files in the cache directory
     * @return bool true
     * @author Alex McFadyen
     */
    
function clearCache($clearAll false){
        
$files glob($this->cache_location.'*.jpg');
        foreach(
$files as $file)
            if(
$clearAll OR time() < filectime($file))
                
unlink($file);
            
        return 
true;
    }

    function 
addError($msg){
        
$this->errors[] = $msg;
    }
}
?>
?>

Drayen
Posted May 1, 2007 by Alex McFadyen
 

Comment

8 Watermarks

Can you add code for watermarking images to that?
Posted Jun 27, 2007 by Satoshi
 

Comment

9 Watermarks

Yes you could, take a look at the phpThumb project itself, they have functions to do just that. I if you come up with an good re-usable solution, please post it here as i'm sure other people would be interested.
Posted Jun 29, 2007 by Alex McFadyen
 

Comment

10 Updated code

Updated and slightly cleaner code

http://bin.cakephp.org/saved/21922
Watermarking would now be easy, look @ #6 and #7 on http://phpthumb.sourceforge.net/demo/demo/phpThumb.demo.demo.php
Posted Jul 18, 2007 by Alex McFadyen
 

Comment

11 Adding Filters for Watermarks and other stuff

Yes you could, take a look at the phpThumb project itself, they have functions to do just that. I if you come up with an good re-usable solution, please post it here as i'm sure other people would be interested.
I changed one of the elements of the presets array in the component
from:

'fltr[]' => null,
to:

'fltr' => null,

Then added two methods to the component that add and remove filters:


    function addFilter($filter){
        if($this->presets['fltr'] != null){
            $this->presets['fltr'][]= $filter;
        }else{
            $this->presets['fltr']= array($filter);
        } 
    }

    function removeFilter($filter = true){
        if($filter === true){
            $this->presets['fltr']= null;

        }else{
            $offset= array_search($filter, $this->presets['fltr']);
            if($offset !== false){
                array_splice($this->presets['fltr'], $offset, 1);
            }            
        }
    }

Usage:
Let's say we want to have a red text placed on the center of the images:

$this->PhpThumb->addFilter('wmt|My Message|20|C|FF0000');

Check out http://phpthumb.sourceforge.net/demo/demo/phpThumb.demo.demo.php for more samples using filters.
Posted Nov 20, 2007 by Rolan