2008-12-15 08:56:18 +00:00
< ? php defined ( 'SYSPATH' ) OR die ( 'No direct access allowed.' );
/**
* Manipulate images using standard methods such as resize , crop , rotate , etc .
* This class must be re - initialized for every image you wish to manipulate .
*
2009-11-24 19:20:36 -08:00
* $Id : Image . php 4679 2009 - 11 - 10 01 : 45 : 52 Z isaiah $
2008-12-15 08:56:18 +00:00
*
* @ package Image
* @ author Kohana Team
2009-11-24 19:20:36 -08:00
* @ copyright ( c ) 2007 - 2009 Kohana Team
* @ license http :// kohanaphp . com / license
2008-12-15 08:56:18 +00:00
*/
class Image_Core {
// Master Dimension
const NONE = 1 ;
const AUTO = 2 ;
const HEIGHT = 3 ;
const WIDTH = 4 ;
2009-11-24 19:20:36 -08:00
2008-12-15 08:56:18 +00:00
// Flip Directions
const HORIZONTAL = 5 ;
const VERTICAL = 6 ;
2009-11-24 19:20:36 -08:00
// Orientations
const PORTRAIT = 7 ;
const LANDSCAPE = 8 ;
const SQUARE = 9 ;
2008-12-15 08:56:18 +00:00
// Allowed image types
public static $allowed_types = array
(
IMAGETYPE_GIF => 'gif' ,
IMAGETYPE_JPEG => 'jpg' ,
IMAGETYPE_PNG => 'png' ,
IMAGETYPE_TIFF_II => 'tiff' ,
IMAGETYPE_TIFF_MM => 'tiff' ,
);
// Driver instance
protected $driver ;
// Driver actions
protected $actions = array ();
// Reference to the current image filename
protected $image = '' ;
/**
* Creates a new Image instance and returns it .
*
* @ param string filename of image
* @ param array non - default configurations
* @ return object
*/
public static function factory ( $image , $config = NULL )
{
return new Image ( $image , $config );
}
/**
* Creates a new image editor instance .
*
* @ throws Kohana_Exception
* @ param string filename of image
* @ param array non - default configurations
* @ return void
*/
public function __construct ( $image , $config = NULL )
{
static $check ;
// Make the check exactly once
( $check === NULL ) and $check = function_exists ( 'getimagesize' );
if ( $check === FALSE )
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The Image library requires the getimagesize() PHP function, which is not available in your installation.' );
2008-12-15 08:56:18 +00:00
// Check to make sure the image exists
if ( ! is_file ( $image ))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The specified image, :image:, was not found. Please verify that images exist by using file_exists() before manipulating them.' , array ( ':image:' => $image ));
2008-12-15 08:56:18 +00:00
// Disable error reporting, to prevent PHP warnings
$ER = error_reporting ( 0 );
// Fetch the image size and mime type
$image_info = getimagesize ( $image );
// Turn on error reporting again
error_reporting ( $ER );
// Make sure that the image is readable and valid
if ( ! is_array ( $image_info ) OR count ( $image_info ) < 3 )
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The file specified, :file:, is not readable or is not an image' , array ( ':file:' => $image ));
2008-12-15 08:56:18 +00:00
// Check to make sure the image type is allowed
if ( ! isset ( Image :: $allowed_types [ $image_info [ 2 ]]))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The specified image, :type:, is not an allowed image type.' , array ( ':type:' => $image ));
2008-12-15 08:56:18 +00:00
// Image has been validated, load it
$this -> image = array
(
'file' => str_replace ( '\\' , '/' , realpath ( $image )),
'width' => $image_info [ 0 ],
'height' => $image_info [ 1 ],
'type' => $image_info [ 2 ],
'ext' => Image :: $allowed_types [ $image_info [ 2 ]],
'mime' => $image_info [ 'mime' ]
);
2009-11-24 19:20:36 -08:00
$this -> determine_orientation ();
2008-12-15 08:56:18 +00:00
// Load configuration
$this -> config = ( array ) $config + Kohana :: config ( 'image' );
// Set driver class name
$driver = 'Image_' . ucfirst ( $this -> config [ 'driver' ]) . '_Driver' ;
// Load the driver
if ( ! Kohana :: auto_load ( $driver ))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The :driver: driver for the :library: library could not be found' ,
array ( ':driver:' => $this -> config [ 'driver' ], ':library:' => get_class ( $this )));
2008-12-15 08:56:18 +00:00
// Initialize the driver
$this -> driver = new $driver ( $this -> config [ 'params' ]);
// Validate the driver
if ( ! ( $this -> driver instanceof Image_Driver ))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The :driver: driver for the :library: library must implement the :interface: interface' ,
array ( ':driver:' => $this -> config [ 'driver' ], ':library:' => get_class ( $this ), ':interface:' => 'Image_Driver' ));
}
/**
* Works out the correct orientation for the image
*
* @ return void
*/
protected function determine_orientation ()
{
switch ( TRUE )
{
case $this -> image [ 'height' ] > $this -> image [ 'width' ] :
$orientation = Image :: PORTRAIT ;
break ;
case $this -> image [ 'height' ] < $this -> image [ 'width' ] :
$orientation = Image :: LANDSCAPE ;
break ;
default :
$orientation = Image :: SQUARE ;
}
$this -> image [ 'orientation' ] = $orientation ;
2008-12-15 08:56:18 +00:00
}
/**
* Handles retrieval of pre - save image properties
*
* @ param string property name
* @ return mixed
*/
public function __get ( $property )
{
if ( isset ( $this -> image [ $property ]))
{
return $this -> image [ $property ];
}
else
{
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The :property: property does not exist in the :class: class.' ,
array ( ':property:' => $property , ':class:' => get_class ( $this )));
2008-12-15 08:56:18 +00:00
}
}
/**
* Resize an image to a specific width and height . By default , Kohana will
* maintain the aspect ratio using the width as the master dimension . If you
* wish to use height as master dim , set $image -> master_dim = Image :: HEIGHT
* This method is chainable .
*
* @ throws Kohana_Exception
* @ param integer width
* @ param integer height
* @ param integer one of : Image :: NONE , Image :: AUTO , Image :: WIDTH , Image :: HEIGHT
* @ return object
*/
public function resize ( $width , $height , $master = NULL )
{
if ( ! $this -> valid_size ( 'width' , $width ))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The width you specified, :width:, is not valid.' , array ( ':width:' => $width ));
2008-12-15 08:56:18 +00:00
if ( ! $this -> valid_size ( 'height' , $height ))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The height you specified, :height:, is not valid.' , array ( ':height:' => $height ));
2008-12-15 08:56:18 +00:00
if ( empty ( $width ) AND empty ( $height ))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The dimensions specified for :function: are not valid.' , array ( ':function:' => __FUNCTION__ ));
2008-12-15 08:56:18 +00:00
if ( $master === NULL )
{
// Maintain the aspect ratio by default
$master = Image :: AUTO ;
}
elseif ( ! $this -> valid_size ( 'master' , $master ))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The master dimension specified is not valid.' );
2008-12-15 08:56:18 +00:00
$this -> actions [ 'resize' ] = array
(
'width' => $width ,
'height' => $height ,
'master' => $master ,
);
2009-11-24 19:20:36 -08:00
$this -> determine_orientation ();
2008-12-15 08:56:18 +00:00
return $this ;
}
/**
* Crop an image to a specific width and height . You may also set the top
* and left offset .
* This method is chainable .
*
* @ throws Kohana_Exception
* @ param integer width
* @ param integer height
* @ param integer top offset , pixel value or one of : top , center , bottom
* @ param integer left offset , pixel value or one of : left , center , right
* @ return object
*/
public function crop ( $width , $height , $top = 'center' , $left = 'center' )
{
if ( ! $this -> valid_size ( 'width' , $width ))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The width you specified, :width:, is not valid.' , array ( ':width:' => $width ));
2008-12-15 08:56:18 +00:00
if ( ! $this -> valid_size ( 'height' , $height ))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The height you specified, :height:, is not valid.' , array ( ':height:' => $height ));
2008-12-15 08:56:18 +00:00
if ( ! $this -> valid_size ( 'top' , $top ))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The top offset you specified, :top:, is not valid.' , array ( ':top:' => $top ));
2008-12-15 08:56:18 +00:00
if ( ! $this -> valid_size ( 'left' , $left ))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The left offset you specified, :left:, is not valid.' , array ( ':left:' => $left ));
2008-12-15 08:56:18 +00:00
if ( empty ( $width ) AND empty ( $height ))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The dimensions specified for :function: are not valid.' , array ( ':function:' => __FUNCTION__ ));
2008-12-15 08:56:18 +00:00
$this -> actions [ 'crop' ] = array
(
'width' => $width ,
'height' => $height ,
'top' => $top ,
'left' => $left ,
);
2009-11-24 19:20:36 -08:00
$this -> determine_orientation ();
2008-12-15 08:56:18 +00:00
return $this ;
}
/**
* Allows rotation of an image by 180 degrees clockwise or counter clockwise .
*
* @ param integer degrees
* @ return object
*/
public function rotate ( $degrees )
{
$degrees = ( int ) $degrees ;
if ( $degrees > 180 )
{
do
{
// Keep subtracting full circles until the degrees have normalized
$degrees -= 360 ;
}
while ( $degrees > 180 );
}
if ( $degrees < - 180 )
{
do
{
// Keep adding full circles until the degrees have normalized
$degrees += 360 ;
}
while ( $degrees < - 180 );
}
$this -> actions [ 'rotate' ] = $degrees ;
return $this ;
}
2008-12-29 23:25:28 +00:00
/**
* Overlay a second image on top of this one .
*
* @ throws Kohana_Exception
* @ param string $overlay_file path to an image file
* @ param integer $x x offset for the overlay
* @ param integer $y y offset for the overlay
* @ param integer $transparency transparency percent
*/
public function composite ( $overlay_file , $x , $y , $transparency )
{
$image_info = getimagesize ( $overlay_file );
// Check to make sure the image type is allowed
if ( ! isset ( Image :: $allowed_types [ $image_info [ 2 ]]))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The specified image, :type:, is not an allowed image type.' , array ( ':type:' => $overlay_file ));
2008-12-29 23:25:28 +00:00
$this -> actions [ 'composite' ] = array
(
'overlay_file' => $overlay_file ,
'mime' => $image_info [ 'mime' ],
'x' => $x ,
'y' => $y ,
'transparency' => $transparency
);
return $this ;
}
2008-12-15 08:56:18 +00:00
/**
* Flip an image horizontally or vertically .
*
* @ throws Kohana_Exception
* @ param integer direction
* @ return object
*/
public function flip ( $direction )
{
2009-03-17 18:56:01 +00:00
if ( $direction !== Image :: HORIZONTAL AND $direction !== Image :: VERTICAL )
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The flip direction specified is not valid.' );
2008-12-15 08:56:18 +00:00
$this -> actions [ 'flip' ] = $direction ;
return $this ;
}
/**
* Change the quality of an image .
*
* @ param integer quality as a percentage
* @ return object
*/
public function quality ( $amount )
{
$this -> actions [ 'quality' ] = max ( 1 , min ( $amount , 100 ));
return $this ;
}
/**
* Sharpen an image .
*
* @ param integer amount to sharpen , usually ~ 20 is ideal
* @ return object
*/
public function sharpen ( $amount )
{
$this -> actions [ 'sharpen' ] = max ( 1 , min ( $amount , 100 ));
return $this ;
}
/**
* Save the image to a new image or overwrite this image .
*
* @ throws Kohana_Exception
* @ param string new image filename
* @ param integer permissions for new image
2008-12-20 07:38:46 +00:00
* @ param boolean keep or discard image process actions
2008-12-15 08:56:18 +00:00
* @ return object
*/
2009-11-24 19:20:36 -08:00
public function save ( $new_image = FALSE , $chmod = 0644 , $keep_actions = FALSE , $background = NULL )
2008-12-15 08:56:18 +00:00
{
// If no new image is defined, use the current image
empty ( $new_image ) and $new_image = $this -> image [ 'file' ];
// Separate the directory and filename
$dir = pathinfo ( $new_image , PATHINFO_DIRNAME );
$file = pathinfo ( $new_image , PATHINFO_BASENAME );
// Normalize the path
$dir = str_replace ( '\\' , '/' , realpath ( $dir )) . '/' ;
if ( ! is_writable ( $dir ))
2009-11-24 19:20:36 -08:00
throw new Kohana_Exception ( 'The specified directory, :dir:, is not writable.' , array ( ':dir:' => $dir ));
2008-12-15 08:56:18 +00:00
2009-11-24 19:20:36 -08:00
if ( $status = $this -> driver -> process ( $this -> image , $this -> actions , $dir , $file , FALSE , $background ))
2008-12-15 08:56:18 +00:00
{
if ( $chmod !== FALSE )
{
// Set permissions
chmod ( $new_image , $chmod );
}
}
2009-03-04 18:15:56 +00:00
2009-11-24 19:20:36 -08:00
if ( $keep_actions !== TRUE )
{
// Reset actions. Subsequent save() or render() will not apply previous actions.
2008-12-20 07:38:46 +00:00
$this -> actions = array ();
2009-11-24 19:20:36 -08:00
}
2009-03-04 18:15:56 +00:00
2008-12-15 08:56:18 +00:00
return $status ;
}
2009-03-04 18:15:56 +00:00
/**
* Output the image to the browser .
*
2008-12-20 07:38:46 +00:00
* @ param boolean keep or discard image process actions
2009-03-04 18:15:56 +00:00
* @ return object
*/
2009-11-24 19:20:36 -08:00
public function render ( $keep_actions = FALSE , $background = NULL )
2009-03-04 18:15:56 +00:00
{
$new_image = $this -> image [ 'file' ];
// Separate the directory and filename
$dir = pathinfo ( $new_image , PATHINFO_DIRNAME );
$file = pathinfo ( $new_image , PATHINFO_BASENAME );
// Normalize the path
$dir = str_replace ( '\\' , '/' , realpath ( $dir )) . '/' ;
// Process the image with the driver
2009-11-24 19:20:36 -08:00
$status = $this -> driver -> process ( $this -> image , $this -> actions , $dir , $file , TRUE , $background );
2009-03-04 18:15:56 +00:00
2009-11-24 19:20:36 -08:00
if ( $keep_actions !== TRUE )
{
// Reset actions. Subsequent save() or render() will not apply previous actions.
2008-12-20 07:38:46 +00:00
$this -> actions = array ();
2009-11-24 19:20:36 -08:00
}
2009-03-04 18:15:56 +00:00
return $status ;
2008-12-20 07:38:46 +00:00
}
2008-12-15 08:56:18 +00:00
/**
* Sanitize a given value type .
*
* @ param string type of property
* @ param mixed property value
* @ return boolean
*/
protected function valid_size ( $type , & $value )
{
if ( is_null ( $value ))
return TRUE ;
if ( ! is_scalar ( $value ))
return FALSE ;
switch ( $type )
{
case 'width' :
case 'height' :
if ( is_string ( $value ) AND ! ctype_digit ( $value ))
{
// Only numbers and percent signs
if ( ! preg_match ( '/^[0-9]++%$/D' , $value ))
return FALSE ;
}
else
{
$value = ( int ) $value ;
}
break ;
case 'top' :
if ( is_string ( $value ) AND ! ctype_digit ( $value ))
{
if ( ! in_array ( $value , array ( 'top' , 'bottom' , 'center' )))
return FALSE ;
}
else
{
$value = ( int ) $value ;
}
break ;
case 'left' :
if ( is_string ( $value ) AND ! ctype_digit ( $value ))
{
if ( ! in_array ( $value , array ( 'left' , 'right' , 'center' )))
return FALSE ;
}
else
{
$value = ( int ) $value ;
}
break ;
case 'master' :
if ( $value !== Image :: NONE AND
$value !== Image :: AUTO AND
$value !== Image :: WIDTH AND
$value !== Image :: HEIGHT )
return FALSE ;
break ;
}
return TRUE ;
}
} // End Image