Kohana v3.3.0

This commit is contained in:
Deon George
2013-04-22 14:09:50 +10:00
commit f96694b18f
1280 changed files with 145034 additions and 0 deletions

View File

View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') OR die('No direct script access.');
abstract class Image extends Kohana_Image {}

View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Image_GD extends Kohana_Image_GD {}

View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') OR die('No direct script access.');
class Image_Imagick extends Kohana_Image_Imagick {}

View File

@@ -0,0 +1,761 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Image manipulation support. Allows images to be resized, cropped, etc.
*
* @package Kohana/Image
* @category Base
* @author Kohana Team
* @copyright (c) 2008-2009 Kohana Team
* @license http://kohanaphp.com/license.html
*/
abstract class Kohana_Image {
// Resizing constraints
const NONE = 0x01;
const WIDTH = 0x02;
const HEIGHT = 0x03;
const AUTO = 0x04;
const INVERSE = 0x05;
const PRECISE = 0x06;
// Flipping directions
const HORIZONTAL = 0x11;
const VERTICAL = 0x12;
/**
* @var string default driver: GD, ImageMagick, etc
*/
public static $default_driver = 'GD';
// Status of the driver check
protected static $_checked = FALSE;
/**
* Loads an image and prepares it for manipulation.
*
* $image = Image::factory('upload/test.jpg');
*
* @param string $file image file path
* @param string $driver driver type: GD, ImageMagick, etc
* @return Image
* @uses Image::$default_driver
*/
public static function factory($file, $driver = NULL)
{
if ($driver === NULL)
{
// Use the default driver
$driver = Image::$default_driver;
}
// Set the class name
$class = 'Image_'.$driver;
return new $class($file);
}
/**
* @var string image file path
*/
public $file;
/**
* @var integer image width
*/
public $width;
/**
* @var integer image height
*/
public $height;
/**
* @var integer one of the IMAGETYPE_* constants
*/
public $type;
/**
* @var string mime type of the image
*/
public $mime;
/**
* Loads information about the image. Will throw an exception if the image
* does not exist or is not an image.
*
* @param string $file image file path
* @return void
* @throws Kohana_Exception
*/
public function __construct($file)
{
try
{
// Get the real path to the file
$file = realpath($file);
// Get the image information
$info = getimagesize($file);
}
catch (Exception $e)
{
// Ignore all errors while reading the image
}
if (empty($file) OR empty($info))
{
throw new Kohana_Exception('Not an image or invalid image: :file',
array(':file' => Debug::path($file)));
}
// Store the image information
$this->file = $file;
$this->width = $info[0];
$this->height = $info[1];
$this->type = $info[2];
$this->mime = image_type_to_mime_type($this->type);
}
/**
* Render the current image.
*
* echo $image;
*
* [!!] The output of this function is binary and must be rendered with the
* appropriate Content-Type header or it will not be displayed correctly!
*
* @return string
*/
public function __toString()
{
try
{
// Render the current image
return $this->render();
}
catch (Exception $e)
{
if (is_object(Kohana::$log))
{
// Get the text of the exception
$error = Kohana_Exception::text($e);
// Add this exception to the log
Kohana::$log->add(Log::ERROR, $error);
}
// Showing any kind of error will be "inside" image data
return '';
}
}
/**
* Resize the image to the given size. Either the width or the height can
* be omitted and the image will be resized proportionally.
*
* // Resize to 200 pixels on the shortest side
* $image->resize(200, 200);
*
* // Resize to 200x200 pixels, keeping aspect ratio
* $image->resize(200, 200, Image::INVERSE);
*
* // Resize to 500 pixel width, keeping aspect ratio
* $image->resize(500, NULL);
*
* // Resize to 500 pixel height, keeping aspect ratio
* $image->resize(NULL, 500);
*
* // Resize to 200x500 pixels, ignoring aspect ratio
* $image->resize(200, 500, Image::NONE);
*
* @param integer $width new width
* @param integer $height new height
* @param integer $master master dimension
* @return $this
* @uses Image::_do_resize
*/
public function resize($width = NULL, $height = NULL, $master = NULL)
{
if ($master === NULL)
{
// Choose the master dimension automatically
$master = Image::AUTO;
}
// Image::WIDTH and Image::HEIGHT deprecated. You can use it in old projects,
// but in new you must pass empty value for non-master dimension
elseif ($master == Image::WIDTH AND ! empty($width))
{
$master = Image::AUTO;
// Set empty height for backward compatibility
$height = NULL;
}
elseif ($master == Image::HEIGHT AND ! empty($height))
{
$master = Image::AUTO;
// Set empty width for backward compatibility
$width = NULL;
}
if (empty($width))
{
if ($master === Image::NONE)
{
// Use the current width
$width = $this->width;
}
else
{
// If width not set, master will be height
$master = Image::HEIGHT;
}
}
if (empty($height))
{
if ($master === Image::NONE)
{
// Use the current height
$height = $this->height;
}
else
{
// If height not set, master will be width
$master = Image::WIDTH;
}
}
switch ($master)
{
case Image::AUTO:
// Choose direction with the greatest reduction ratio
$master = ($this->width / $width) > ($this->height / $height) ? Image::WIDTH : Image::HEIGHT;
break;
case Image::INVERSE:
// Choose direction with the minimum reduction ratio
$master = ($this->width / $width) > ($this->height / $height) ? Image::HEIGHT : Image::WIDTH;
break;
}
switch ($master)
{
case Image::WIDTH:
// Recalculate the height based on the width proportions
$height = $this->height * $width / $this->width;
break;
case Image::HEIGHT:
// Recalculate the width based on the height proportions
$width = $this->width * $height / $this->height;
break;
case Image::PRECISE:
// Resize to precise size
$ratio = $this->width / $this->height;
if ($width / $height > $ratio)
{
$height = $this->height * $width / $this->width;
}
else
{
$width = $this->width * $height / $this->height;
}
break;
}
// Convert the width and height to integers, minimum value is 1px
$width = max(round($width), 1);
$height = max(round($height), 1);
$this->_do_resize($width, $height);
return $this;
}
/**
* Crop an image to the given size. Either the width or the height can be
* omitted and the current width or height will be used.
*
* If no offset is specified, the center of the axis will be used.
* If an offset of TRUE is specified, the bottom of the axis will be used.
*
* // Crop the image to 200x200 pixels, from the center
* $image->crop(200, 200);
*
* @param integer $width new width
* @param integer $height new height
* @param mixed $offset_x offset from the left
* @param mixed $offset_y offset from the top
* @return $this
* @uses Image::_do_crop
*/
public function crop($width, $height, $offset_x = NULL, $offset_y = NULL)
{
if ($width > $this->width)
{
// Use the current width
$width = $this->width;
}
if ($height > $this->height)
{
// Use the current height
$height = $this->height;
}
if ($offset_x === NULL)
{
// Center the X offset
$offset_x = round(($this->width - $width) / 2);
}
elseif ($offset_x === TRUE)
{
// Bottom the X offset
$offset_x = $this->width - $width;
}
elseif ($offset_x < 0)
{
// Set the X offset from the right
$offset_x = $this->width - $width + $offset_x;
}
if ($offset_y === NULL)
{
// Center the Y offset
$offset_y = round(($this->height - $height) / 2);
}
elseif ($offset_y === TRUE)
{
// Bottom the Y offset
$offset_y = $this->height - $height;
}
elseif ($offset_y < 0)
{
// Set the Y offset from the bottom
$offset_y = $this->height - $height + $offset_y;
}
// Determine the maximum possible width and height
$max_width = $this->width - $offset_x;
$max_height = $this->height - $offset_y;
if ($width > $max_width)
{
// Use the maximum available width
$width = $max_width;
}
if ($height > $max_height)
{
// Use the maximum available height
$height = $max_height;
}
$this->_do_crop($width, $height, $offset_x, $offset_y);
return $this;
}
/**
* Rotate the image by a given amount.
*
* // Rotate 45 degrees clockwise
* $image->rotate(45);
*
* // Rotate 90% counter-clockwise
* $image->rotate(-90);
*
* @param integer $degrees degrees to rotate: -360-360
* @return $this
* @uses Image::_do_rotate
*/
public function rotate($degrees)
{
// Make the degrees an integer
$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->_do_rotate($degrees);
return $this;
}
/**
* Flip the image along the horizontal or vertical axis.
*
* // Flip the image from top to bottom
* $image->flip(Image::HORIZONTAL);
*
* // Flip the image from left to right
* $image->flip(Image::VERTICAL);
*
* @param integer $direction direction: Image::HORIZONTAL, Image::VERTICAL
* @return $this
* @uses Image::_do_flip
*/
public function flip($direction)
{
if ($direction !== Image::HORIZONTAL)
{
// Flip vertically
$direction = Image::VERTICAL;
}
$this->_do_flip($direction);
return $this;
}
/**
* Sharpen the image by a given amount.
*
* // Sharpen the image by 20%
* $image->sharpen(20);
*
* @param integer $amount amount to sharpen: 1-100
* @return $this
* @uses Image::_do_sharpen
*/
public function sharpen($amount)
{
// The amount must be in the range of 1 to 100
$amount = min(max($amount, 1), 100);
$this->_do_sharpen($amount);
return $this;
}
/**
* Add a reflection to an image. The most opaque part of the reflection
* will be equal to the opacity setting and fade out to full transparent.
* Alpha transparency is preserved.
*
* // Create a 50 pixel reflection that fades from 0-100% opacity
* $image->reflection(50);
*
* // Create a 50 pixel reflection that fades from 100-0% opacity
* $image->reflection(50, 100, TRUE);
*
* // Create a 50 pixel reflection that fades from 0-60% opacity
* $image->reflection(50, 60, TRUE);
*
* [!!] By default, the reflection will be go from transparent at the top
* to opaque at the bottom.
*
* @param integer $height reflection height
* @param integer $opacity reflection opacity: 0-100
* @param boolean $fade_in TRUE to fade in, FALSE to fade out
* @return $this
* @uses Image::_do_reflection
*/
public function reflection($height = NULL, $opacity = 100, $fade_in = FALSE)
{
if ($height === NULL OR $height > $this->height)
{
// Use the current height
$height = $this->height;
}
// The opacity must be in the range of 0 to 100
$opacity = min(max($opacity, 0), 100);
$this->_do_reflection($height, $opacity, $fade_in);
return $this;
}
/**
* Add a watermark to an image with a specified opacity. Alpha transparency
* will be preserved.
*
* If no offset is specified, the center of the axis will be used.
* If an offset of TRUE is specified, the bottom of the axis will be used.
*
* // Add a watermark to the bottom right of the image
* $mark = Image::factory('upload/watermark.png');
* $image->watermark($mark, TRUE, TRUE);
*
* @param Image $watermark watermark Image instance
* @param integer $offset_x offset from the left
* @param integer $offset_y offset from the top
* @param integer $opacity opacity of watermark: 1-100
* @return $this
* @uses Image::_do_watermark
*/
public function watermark(Image $watermark, $offset_x = NULL, $offset_y = NULL, $opacity = 100)
{
if ($offset_x === NULL)
{
// Center the X offset
$offset_x = round(($this->width - $watermark->width) / 2);
}
elseif ($offset_x === TRUE)
{
// Bottom the X offset
$offset_x = $this->width - $watermark->width;
}
elseif ($offset_x < 0)
{
// Set the X offset from the right
$offset_x = $this->width - $watermark->width + $offset_x;
}
if ($offset_y === NULL)
{
// Center the Y offset
$offset_y = round(($this->height - $watermark->height) / 2);
}
elseif ($offset_y === TRUE)
{
// Bottom the Y offset
$offset_y = $this->height - $watermark->height;
}
elseif ($offset_y < 0)
{
// Set the Y offset from the bottom
$offset_y = $this->height - $watermark->height + $offset_y;
}
// The opacity must be in the range of 1 to 100
$opacity = min(max($opacity, 1), 100);
$this->_do_watermark($watermark, $offset_x, $offset_y, $opacity);
return $this;
}
/**
* Set the background color of an image. This is only useful for images
* with alpha transparency.
*
* // Make the image background black
* $image->background('#000');
*
* // Make the image background black with 50% opacity
* $image->background('#000', 50);
*
* @param string $color hexadecimal color value
* @param integer $opacity background opacity: 0-100
* @return $this
* @uses Image::_do_background
*/
public function background($color, $opacity = 100)
{
if ($color[0] === '#')
{
// Remove the pound
$color = substr($color, 1);
}
if (strlen($color) === 3)
{
// Convert shorthand into longhand hex notation
$color = preg_replace('/./', '$0$0', $color);
}
// Convert the hex into RGB values
list ($r, $g, $b) = array_map('hexdec', str_split($color, 2));
// The opacity must be in the range of 0 to 100
$opacity = min(max($opacity, 0), 100);
$this->_do_background($r, $g, $b, $opacity);
return $this;
}
/**
* Save the image. If the filename is omitted, the original image will
* be overwritten.
*
* // Save the image as a PNG
* $image->save('saved/cool.png');
*
* // Overwrite the original image
* $image->save();
*
* [!!] If the file exists, but is not writable, an exception will be thrown.
*
* [!!] If the file does not exist, and the directory is not writable, an
* exception will be thrown.
*
* @param string $file new image path
* @param integer $quality quality of image: 1-100
* @return boolean
* @uses Image::_save
* @throws Kohana_Exception
*/
public function save($file = NULL, $quality = 100)
{
if ($file === NULL)
{
// Overwrite the file
$file = $this->file;
}
if (is_file($file))
{
if ( ! is_writable($file))
{
throw new Kohana_Exception('File must be writable: :file',
array(':file' => Debug::path($file)));
}
}
else
{
// Get the directory of the file
$directory = realpath(pathinfo($file, PATHINFO_DIRNAME));
if ( ! is_dir($directory) OR ! is_writable($directory))
{
throw new Kohana_Exception('Directory must be writable: :directory',
array(':directory' => Debug::path($directory)));
}
}
// The quality must be in the range of 1 to 100
$quality = min(max($quality, 1), 100);
return $this->_do_save($file, $quality);
}
/**
* Render the image and return the binary string.
*
* // Render the image at 50% quality
* $data = $image->render(NULL, 50);
*
* // Render the image as a PNG
* $data = $image->render('png');
*
* @param string $type image type to return: png, jpg, gif, etc
* @param integer $quality quality of image: 1-100
* @return string
* @uses Image::_do_render
*/
public function render($type = NULL, $quality = 100)
{
if ($type === NULL)
{
// Use the current image type
$type = image_type_to_extension($this->type, FALSE);
}
return $this->_do_render($type, $quality);
}
/**
* Execute a resize.
*
* @param integer $width new width
* @param integer $height new height
* @return void
*/
abstract protected function _do_resize($width, $height);
/**
* Execute a crop.
*
* @param integer $width new width
* @param integer $height new height
* @param integer $offset_x offset from the left
* @param integer $offset_y offset from the top
* @return void
*/
abstract protected function _do_crop($width, $height, $offset_x, $offset_y);
/**
* Execute a rotation.
*
* @param integer $degrees degrees to rotate
* @return void
*/
abstract protected function _do_rotate($degrees);
/**
* Execute a flip.
*
* @param integer $direction direction to flip
* @return void
*/
abstract protected function _do_flip($direction);
/**
* Execute a sharpen.
*
* @param integer $amount amount to sharpen
* @return void
*/
abstract protected function _do_sharpen($amount);
/**
* Execute a reflection.
*
* @param integer $height reflection height
* @param integer $opacity reflection opacity
* @param boolean $fade_in TRUE to fade out, FALSE to fade in
* @return void
*/
abstract protected function _do_reflection($height, $opacity, $fade_in);
/**
* Execute a watermarking.
*
* @param Image $image watermarking Image
* @param integer $offset_x offset from the left
* @param integer $offset_y offset from the top
* @param integer $opacity opacity of watermark
* @return void
*/
abstract protected function _do_watermark(Image $image, $offset_x, $offset_y, $opacity);
/**
* Execute a background.
*
* @param integer $r red
* @param integer $g green
* @param integer $b blue
* @param integer $opacity opacity
* @return void
*/
abstract protected function _do_background($r, $g, $b, $opacity);
/**
* Execute a save.
*
* @param string $file new image filename
* @param integer $quality quality
* @return boolean
*/
abstract protected function _do_save($file, $quality);
/**
* Execute a render.
*
* @param string $type image type: png, jpg, gif, etc
* @param integer $quality quality
* @return string
*/
abstract protected function _do_render($type, $quality);
} // End Image

View File

@@ -0,0 +1,665 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Support for image manipulation using [GD](http://php.net/GD).
*
* @package Kohana/Image
* @category Drivers
* @author Kohana Team
* @copyright (c) 2008-2009 Kohana Team
* @license http://kohanaphp.com/license.html
*/
class Kohana_Image_GD extends Image {
// Is GD bundled or separate?
protected static $_bundled;
/**
* Checks if GD is enabled and bundled. Bundled GD is required for some
* methods to work. Exceptions will be thrown from those methods when GD is
* not bundled.
*
* @return boolean
*/
public static function check()
{
if ( ! function_exists('gd_info'))
{
throw new Kohana_Exception('GD is either not installed or not enabled, check your configuration');
}
if (defined('GD_BUNDLED'))
{
// Get the version via a constant, available in PHP 5.
Image_GD::$_bundled = GD_BUNDLED;
}
else
{
// Get the version information
$info = gd_info();
// Extract the bundled status
Image_GD::$_bundled = (bool) preg_match('/\bbundled\b/i', $info['GD Version']);
}
if (defined('GD_VERSION'))
{
// Get the version via a constant, available in PHP 5.2.4+
$version = GD_VERSION;
}
else
{
// Get the version information
$info = gd_info();
// Extract the version number
preg_match('/\d+\.\d+(?:\.\d+)?/', $info['GD Version'], $matches);
// Get the major version
$version = $matches[0];
}
if ( ! version_compare($version, '2.0.1', '>='))
{
throw new Kohana_Exception('Image_GD requires GD version :required or greater, you have :version',
array('required' => '2.0.1', ':version' => $version));
}
return Image_GD::$_checked = TRUE;
}
// Temporary image resource
protected $_image;
// Function name to open Image
protected $_create_function;
/**
* Runs [Image_GD::check] and loads the image.
*
* @param string $file image file path
* @return void
* @throws Kohana_Exception
*/
public function __construct($file)
{
if ( ! Image_GD::$_checked)
{
// Run the install check
Image_GD::check();
}
parent::__construct($file);
// Set the image creation function name
switch ($this->type)
{
case IMAGETYPE_JPEG:
$create = 'imagecreatefromjpeg';
break;
case IMAGETYPE_GIF:
$create = 'imagecreatefromgif';
break;
case IMAGETYPE_PNG:
$create = 'imagecreatefrompng';
break;
}
if ( ! isset($create) OR ! function_exists($create))
{
throw new Kohana_Exception('Installed GD does not support :type images',
array(':type' => image_type_to_extension($this->type, FALSE)));
}
// Save function for future use
$this->_create_function = $create;
// Save filename for lazy loading
$this->_image = $this->file;
}
/**
* Destroys the loaded image to free up resources.
*
* @return void
*/
public function __destruct()
{
if (is_resource($this->_image))
{
// Free all resources
imagedestroy($this->_image);
}
}
/**
* Loads an image into GD.
*
* @return void
*/
protected function _load_image()
{
if ( ! is_resource($this->_image))
{
// Gets create function
$create = $this->_create_function;
// Open the temporary image
$this->_image = $create($this->file);
// Preserve transparency when saving
imagesavealpha($this->_image, TRUE);
}
}
/**
* Execute a resize.
*
* @param integer $width new width
* @param integer $height new height
* @return void
*/
protected function _do_resize($width, $height)
{
// Presize width and height
$pre_width = $this->width;
$pre_height = $this->height;
// Loads image if not yet loaded
$this->_load_image();
// Test if we can do a resize without resampling to speed up the final resize
if ($width > ($this->width / 2) AND $height > ($this->height / 2))
{
// The maximum reduction is 10% greater than the final size
$reduction_width = round($width * 1.1);
$reduction_height = round($height * 1.1);
while ($pre_width / 2 > $reduction_width AND $pre_height / 2 > $reduction_height)
{
// Reduce the size using an O(2n) algorithm, until it reaches the maximum reduction
$pre_width /= 2;
$pre_height /= 2;
}
// Create the temporary image to copy to
$image = $this->_create($pre_width, $pre_height);
if (imagecopyresized($image, $this->_image, 0, 0, 0, 0, $pre_width, $pre_height, $this->width, $this->height))
{
// Swap the new image for the old one
imagedestroy($this->_image);
$this->_image = $image;
}
}
// Create the temporary image to copy to
$image = $this->_create($width, $height);
// Execute the resize
if (imagecopyresampled($image, $this->_image, 0, 0, 0, 0, $width, $height, $pre_width, $pre_height))
{
// Swap the new image for the old one
imagedestroy($this->_image);
$this->_image = $image;
// Reset the width and height
$this->width = imagesx($image);
$this->height = imagesy($image);
}
}
/**
* Execute a crop.
*
* @param integer $width new width
* @param integer $height new height
* @param integer $offset_x offset from the left
* @param integer $offset_y offset from the top
* @return void
*/
protected function _do_crop($width, $height, $offset_x, $offset_y)
{
// Create the temporary image to copy to
$image = $this->_create($width, $height);
// Loads image if not yet loaded
$this->_load_image();
// Execute the crop
if (imagecopyresampled($image, $this->_image, 0, 0, $offset_x, $offset_y, $width, $height, $width, $height))
{
// Swap the new image for the old one
imagedestroy($this->_image);
$this->_image = $image;
// Reset the width and height
$this->width = imagesx($image);
$this->height = imagesy($image);
}
}
/**
* Execute a rotation.
*
* @param integer $degrees degrees to rotate
* @return void
*/
protected function _do_rotate($degrees)
{
if ( ! Image_GD::$_bundled)
{
throw new Kohana_Exception('This method requires :function, which is only available in the bundled version of GD',
array(':function' => 'imagerotate'));
}
// Loads image if not yet loaded
$this->_load_image();
// Transparent black will be used as the background for the uncovered region
$transparent = imagecolorallocatealpha($this->_image, 0, 0, 0, 127);
// Rotate, setting the transparent color
$image = imagerotate($this->_image, 360 - $degrees, $transparent, 1);
// Save the alpha of the rotated image
imagesavealpha($image, TRUE);
// Get the width and height of the rotated image
$width = imagesx($image);
$height = imagesy($image);
if (imagecopymerge($this->_image, $image, 0, 0, 0, 0, $width, $height, 100))
{
// Swap the new image for the old one
imagedestroy($this->_image);
$this->_image = $image;
// Reset the width and height
$this->width = $width;
$this->height = $height;
}
}
/**
* Execute a flip.
*
* @param integer $direction direction to flip
* @return void
*/
protected function _do_flip($direction)
{
// Create the flipped image
$flipped = $this->_create($this->width, $this->height);
// Loads image if not yet loaded
$this->_load_image();
if ($direction === Image::HORIZONTAL)
{
for ($x = 0; $x < $this->width; $x++)
{
// Flip each row from top to bottom
imagecopy($flipped, $this->_image, $x, 0, $this->width - $x - 1, 0, 1, $this->height);
}
}
else
{
for ($y = 0; $y < $this->height; $y++)
{
// Flip each column from left to right
imagecopy($flipped, $this->_image, 0, $y, 0, $this->height - $y - 1, $this->width, 1);
}
}
// Swap the new image for the old one
imagedestroy($this->_image);
$this->_image = $flipped;
// Reset the width and height
$this->width = imagesx($flipped);
$this->height = imagesy($flipped);
}
/**
* Execute a sharpen.
*
* @param integer $amount amount to sharpen
* @return void
*/
protected function _do_sharpen($amount)
{
if ( ! Image_GD::$_bundled)
{
throw new Kohana_Exception('This method requires :function, which is only available in the bundled version of GD',
array(':function' => 'imageconvolution'));
}
// Loads image if not yet loaded
$this->_load_image();
// Amount should be in the range of 18-10
$amount = round(abs(-18 + ($amount * 0.08)), 2);
// Gaussian blur matrix
$matrix = array
(
array(-1, -1, -1),
array(-1, $amount, -1),
array(-1, -1, -1),
);
// Perform the sharpen
if (imageconvolution($this->_image, $matrix, $amount - 8, 0))
{
// Reset the width and height
$this->width = imagesx($this->_image);
$this->height = imagesy($this->_image);
}
}
/**
* Execute a reflection.
*
* @param integer $height reflection height
* @param integer $opacity reflection opacity
* @param boolean $fade_in TRUE to fade out, FALSE to fade in
* @return void
*/
protected function _do_reflection($height, $opacity, $fade_in)
{
if ( ! Image_GD::$_bundled)
{
throw new Kohana_Exception('This method requires :function, which is only available in the bundled version of GD',
array(':function' => 'imagefilter'));
}
// Loads image if not yet loaded
$this->_load_image();
// Convert an opacity range of 0-100 to 127-0
$opacity = round(abs(($opacity * 127 / 100) - 127));
if ($opacity < 127)
{
// Calculate the opacity stepping
$stepping = (127 - $opacity) / $height;
}
else
{
// Avoid a "divide by zero" error
$stepping = 127 / $height;
}
// Create the reflection image
$reflection = $this->_create($this->width, $this->height + $height);
// Copy the image to the reflection
imagecopy($reflection, $this->_image, 0, 0, 0, 0, $this->width, $this->height);
for ($offset = 0; $height >= $offset; $offset++)
{
// Read the next line down
$src_y = $this->height - $offset - 1;
// Place the line at the bottom of the reflection
$dst_y = $this->height + $offset;
if ($fade_in === TRUE)
{
// Start with the most transparent line first
$dst_opacity = round($opacity + ($stepping * ($height - $offset)));
}
else
{
// Start with the most opaque line first
$dst_opacity = round($opacity + ($stepping * $offset));
}
// Create a single line of the image
$line = $this->_create($this->width, 1);
// Copy a single line from the current image into the line
imagecopy($line, $this->_image, 0, 0, 0, $src_y, $this->width, 1);
// Colorize the line to add the correct alpha level
imagefilter($line, IMG_FILTER_COLORIZE, 0, 0, 0, $dst_opacity);
// Copy a the line into the reflection
imagecopy($reflection, $line, 0, $dst_y, 0, 0, $this->width, 1);
}
// Swap the new image for the old one
imagedestroy($this->_image);
$this->_image = $reflection;
// Reset the width and height
$this->width = imagesx($reflection);
$this->height = imagesy($reflection);
}
/**
* Execute a watermarking.
*
* @param Image $image watermarking Image
* @param integer $offset_x offset from the left
* @param integer $offset_y offset from the top
* @param integer $opacity opacity of watermark
* @return void
*/
protected function _do_watermark(Image $watermark, $offset_x, $offset_y, $opacity)
{
if ( ! Image_GD::$_bundled)
{
throw new Kohana_Exception('This method requires :function, which is only available in the bundled version of GD',
array(':function' => 'imagelayereffect'));
}
// Loads image if not yet loaded
$this->_load_image();
// Create the watermark image resource
$overlay = imagecreatefromstring($watermark->render());
imagesavealpha($overlay, TRUE);
// Get the width and height of the watermark
$width = imagesx($overlay);
$height = imagesy($overlay);
if ($opacity < 100)
{
// Convert an opacity range of 0-100 to 127-0
$opacity = round(abs(($opacity * 127 / 100) - 127));
// Allocate transparent gray
$color = imagecolorallocatealpha($overlay, 127, 127, 127, $opacity);
// The transparent image will overlay the watermark
imagelayereffect($overlay, IMG_EFFECT_OVERLAY);
// Fill the background with the transparent color
imagefilledrectangle($overlay, 0, 0, $width, $height, $color);
}
// Alpha blending must be enabled on the background!
imagealphablending($this->_image, TRUE);
if (imagecopy($this->_image, $overlay, $offset_x, $offset_y, 0, 0, $width, $height))
{
// Destroy the overlay image
imagedestroy($overlay);
}
}
/**
* Execute a background.
*
* @param integer $r red
* @param integer $g green
* @param integer $b blue
* @param integer $opacity opacity
* @return void
*/
protected function _do_background($r, $g, $b, $opacity)
{
// Loads image if not yet loaded
$this->_load_image();
// Convert an opacity range of 0-100 to 127-0
$opacity = round(abs(($opacity * 127 / 100) - 127));
// Create a new background
$background = $this->_create($this->width, $this->height);
// Allocate the color
$color = imagecolorallocatealpha($background, $r, $g, $b, $opacity);
// Fill the image with white
imagefilledrectangle($background, 0, 0, $this->width, $this->height, $color);
// Alpha blending must be enabled on the background!
imagealphablending($background, TRUE);
// Copy the image onto a white background to remove all transparency
if (imagecopy($background, $this->_image, 0, 0, 0, 0, $this->width, $this->height))
{
// Swap the new image for the old one
imagedestroy($this->_image);
$this->_image = $background;
}
}
/**
* Execute a save.
*
* @param string $file new image filename
* @param integer $quality quality
* @return boolean
*/
protected function _do_save($file, $quality)
{
// Loads image if not yet loaded
$this->_load_image();
// Get the extension of the file
$extension = pathinfo($file, PATHINFO_EXTENSION);
// Get the save function and IMAGETYPE
list($save, $type) = $this->_save_function($extension, $quality);
// Save the image to a file
$status = isset($quality) ? $save($this->_image, $file, $quality) : $save($this->_image, $file);
if ($status === TRUE AND $type !== $this->type)
{
// Reset the image type and mime type
$this->type = $type;
$this->mime = image_type_to_mime_type($type);
}
return TRUE;
}
/**
* Execute a render.
*
* @param string $type image type: png, jpg, gif, etc
* @param integer $quality quality
* @return string
*/
protected function _do_render($type, $quality)
{
// Loads image if not yet loaded
$this->_load_image();
// Get the save function and IMAGETYPE
list($save, $type) = $this->_save_function($type, $quality);
// Capture the output
ob_start();
// Render the image
$status = isset($quality) ? $save($this->_image, NULL, $quality) : $save($this->_image, NULL);
if ($status === TRUE AND $type !== $this->type)
{
// Reset the image type and mime type
$this->type = $type;
$this->mime = image_type_to_mime_type($type);
}
return ob_get_clean();
}
/**
* Get the GD saving function and image type for this extension.
* Also normalizes the quality setting
*
* @param string $extension image type: png, jpg, etc
* @param integer $quality image quality
* @return array save function, IMAGETYPE_* constant
* @throws Kohana_Exception
*/
protected function _save_function($extension, & $quality)
{
if ( ! $extension)
{
// Use the current image type
$extension = image_type_to_extension($this->type, FALSE);
}
switch (strtolower($extension))
{
case 'jpg':
case 'jpeg':
// Save a JPG file
$save = 'imagejpeg';
$type = IMAGETYPE_JPEG;
break;
case 'gif':
// Save a GIF file
$save = 'imagegif';
$type = IMAGETYPE_GIF;
// GIFs do not a quality setting
$quality = NULL;
break;
case 'png':
// Save a PNG file
$save = 'imagepng';
$type = IMAGETYPE_PNG;
// Use a compression level of 9 (does not affect quality!)
$quality = 9;
break;
default:
throw new Kohana_Exception('Installed GD does not support :type images',
array(':type' => $extension));
break;
}
return array($save, $type);
}
/**
* Create an empty image with the given width and height.
*
* @param integer $width image width
* @param integer $height image height
* @return resource
*/
protected function _create($width, $height)
{
// Create an empty image
$image = imagecreatetruecolor($width, $height);
// Do not apply alpha blending
imagealphablending($image, FALSE);
// Save alpha levels
imagesavealpha($image, TRUE);
return $image;
}
} // End Image_GD

View File

@@ -0,0 +1,334 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Support for image manipulation using [Imagick](http://php.net/Imagick).
*
* @package Kohana/Image
* @category Drivers
* @author Tamas Mihalik tamas.mihalik@gmail.com
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaphp.com/license.html
*/
class Kohana_Image_Imagick extends Image {
/**
* @var Imagick image magick object
*/
protected $im;
/**
* Checks if ImageMagick is enabled.
*
* @throws Kohana_Exception
* @return boolean
*/
public static function check()
{
if ( ! extension_loaded('imagick'))
{
throw new Kohana_Exception('Imagick is not installed, or the extension is not loaded');
}
return Image_Imagick::$_checked = TRUE;
}
/**
* Runs [Image_Imagick::check] and loads the image.
*
* @return void
* @throws Kohana_Exception
*/
public function __construct($file)
{
if ( ! Image_Imagick::$_checked)
{
// Run the install check
Image_Imagick::check();
}
parent::__construct($file);
$this->im = new Imagick;
$this->im->readImage($file);
if ( ! $this->im->getImageAlphaChannel())
{
// Force the image to have an alpha channel
$this->im->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET);
}
}
/**
* Destroys the loaded image to free up resources.
*
* @return void
*/
public function __destruct()
{
$this->im->clear();
$this->im->destroy();
}
protected function _do_resize($width, $height)
{
if ($this->im->scaleImage($width, $height))
{
// Reset the width and height
$this->width = $this->im->getImageWidth();
$this->height = $this->im->getImageHeight();
return TRUE;
}
return FALSE;
}
protected function _do_crop($width, $height, $offset_x, $offset_y)
{
if ($this->im->cropImage($width, $height, $offset_x, $offset_y))
{
// Reset the width and height
$this->width = $this->im->getImageWidth();
$this->height = $this->im->getImageHeight();
// Trim off hidden areas
$this->im->setImagePage($this->width, $this->height, 0, 0);
return TRUE;
}
return FALSE;
}
protected function _do_rotate($degrees)
{
if ($this->im->rotateImage(new ImagickPixel('transparent'), $degrees))
{
// Reset the width and height
$this->width = $this->im->getImageWidth();
$this->height = $this->im->getImageHeight();
// Trim off hidden areas
$this->im->setImagePage($this->width, $this->height, 0, 0);
return TRUE;
}
return FALSE;
}
protected function _do_flip($direction)
{
if ($direction === Image::HORIZONTAL)
{
return $this->im->flopImage();
}
else
{
return $this->im->flipImage();
}
}
protected function _do_sharpen($amount)
{
// IM not support $amount under 5 (0.15)
$amount = ($amount < 5) ? 5 : $amount;
// Amount should be in the range of 0.0 to 3.0
$amount = ($amount * 3.0) / 100;
return $this->im->sharpenImage(0, $amount);
}
protected function _do_reflection($height, $opacity, $fade_in)
{
// Clone the current image and flip it for reflection
$reflection = $this->im->clone();
$reflection->flipImage();
// Crop the reflection to the selected height
$reflection->cropImage($this->width, $height, 0, 0);
$reflection->setImagePage($this->width, $height, 0, 0);
// Select the fade direction
$direction = array('transparent', 'black');
if ($fade_in)
{
// Change the direction of the fade
$direction = array_reverse($direction);
}
// Create a gradient for fading
$fade = new Imagick;
$fade->newPseudoImage($reflection->getImageWidth(), $reflection->getImageHeight(), vsprintf('gradient:%s-%s', $direction));
// Apply the fade alpha channel to the reflection
$reflection->compositeImage($fade, Imagick::COMPOSITE_DSTOUT, 0, 0);
// NOTE: Using setImageOpacity will destroy alpha channels!
$reflection->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA);
// Create a new container to hold the image and reflection
$image = new Imagick;
$image->newImage($this->width, $this->height + $height, new ImagickPixel);
// Force the image to have an alpha channel
$image->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET);
// Force the background color to be transparent
// $image->setImageBackgroundColor(new ImagickPixel('transparent'));
// Match the colorspace between the two images before compositing
$image->setColorspace($this->im->getColorspace());
// Place the image and reflection into the container
if ($image->compositeImage($this->im, Imagick::COMPOSITE_SRC, 0, 0)
AND $image->compositeImage($reflection, Imagick::COMPOSITE_OVER, 0, $this->height))
{
// Replace the current image with the reflected image
$this->im = $image;
// Reset the width and height
$this->width = $this->im->getImageWidth();
$this->height = $this->im->getImageHeight();
return TRUE;
}
return FALSE;
}
protected function _do_watermark(Image $image, $offset_x, $offset_y, $opacity)
{
// Convert the Image intance into an Imagick instance
$watermark = new Imagick;
$watermark->readImageBlob($image->render(), $image->file);
if ($watermark->getImageAlphaChannel() !== Imagick::ALPHACHANNEL_ACTIVATE)
{
// Force the image to have an alpha channel
$watermark->setImageAlphaChannel(Imagick::ALPHACHANNEL_OPAQUE);
}
if ($opacity < 100)
{
// NOTE: Using setImageOpacity will destroy current alpha channels!
$watermark->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA);
}
// Match the colorspace between the two images before compositing
// $watermark->setColorspace($this->im->getColorspace());
// Apply the watermark to the image
return $this->im->compositeImage($watermark, Imagick::COMPOSITE_DISSOLVE, $offset_x, $offset_y);
}
protected function _do_background($r, $g, $b, $opacity)
{
// Create a RGB color for the background
$color = sprintf('rgb(%d, %d, %d)', $r, $g, $b);
// Create a new image for the background
$background = new Imagick;
$background->newImage($this->width, $this->height, new ImagickPixel($color));
if ( ! $background->getImageAlphaChannel())
{
// Force the image to have an alpha channel
$background->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET);
}
// Clear the background image
$background->setImageBackgroundColor(new ImagickPixel('transparent'));
// NOTE: Using setImageOpacity will destroy current alpha channels!
$background->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA);
// Match the colorspace between the two images before compositing
$background->setColorspace($this->im->getColorspace());
if ($background->compositeImage($this->im, Imagick::COMPOSITE_DISSOLVE, 0, 0))
{
// Replace the current image with the new image
$this->im = $background;
return TRUE;
}
return FALSE;
}
protected function _do_save($file, $quality)
{
// Get the image format and type
list($format, $type) = $this->_get_imagetype(pathinfo($file, PATHINFO_EXTENSION));
// Set the output image type
$this->im->setFormat($format);
// Set the output quality
$this->im->setImageCompressionQuality($quality);
if ($this->im->writeImage($file))
{
// Reset the image type and mime type
$this->type = $type;
$this->mime = image_type_to_mime_type($type);
return TRUE;
}
return FALSE;
}
protected function _do_render($type, $quality)
{
// Get the image format and type
list($format, $type) = $this->_get_imagetype($type);
// Set the output image type
$this->im->setFormat($format);
// Set the output quality
$this->im->setImageCompressionQuality($quality);
// Reset the image type and mime type
$this->type = $type;
$this->mime = image_type_to_mime_type($type);
return (string) $this->im;
}
/**
* Get the image type and format for an extension.
*
* @param string $extension image extension: png, jpg, etc
* @return string IMAGETYPE_* constant
* @throws Kohana_Exception
*/
protected function _get_imagetype($extension)
{
// Normalize the extension to a format
$format = strtolower($extension);
switch ($format)
{
case 'jpg':
case 'jpeg':
$type = IMAGETYPE_JPEG;
break;
case 'gif':
$type = IMAGETYPE_GIF;
break;
case 'png':
$type = IMAGETYPE_PNG;
break;
default:
throw new Kohana_Exception('Installed ImageMagick does not support :type images',
array(':type' => $extension));
break;
}
return array($format, $type);
}
} // End Kohana_Image_Imagick

View File

@@ -0,0 +1,23 @@
<?php defined('SYSPATH') OR die('No direct script access.');
return array(
// Leave this alone
'modules' => array(
// This should be the path to this modules userguide pages, without the 'guide/'. Ex: '/guide/modulename/' would be 'modulename'
'image' => array(
// Whether this modules userguide pages should be shown
'enabled' => TRUE,
// The name that should show up on the userguide index page
'name' => 'Image',
// A short description of this module, shown on the index page
'description' => 'Image manipulation.',
// Copyright message, shown in the footer for this module
'copyright' => '&copy; 20082012 Kohana Team',
)
)
);

View File

@@ -0,0 +1,7 @@
# Examples
The following are mini applications that uses the [Image] module. They are very straight forward and did not include additional code such as validations and the like. They are designed to be simple and should work out of the box.
* [Uploading image](examples/upload)
* [Cropping profile images](examples/crop)
* [Serving images with dynamic dimension](examples/dynamic)

View File

@@ -0,0 +1,141 @@
# Crop Profile Image
This example is very similar to our previous example and even uses the same upload logics. The only difference is that the uploaded image is cropped to square from the center whose dimension is half the original height of the image.
## Controller
We name our new controller as `Controller_Crop` and accessible through `/crop` URL. Assuming that your project is located at [http://localhost/kohana](http://localhost/kohana), then our crop controller is at [http://localhost/kohana/crop](http://localhost/kohana/crop).
~~~
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_Crop extends Controller {
public function action_index()
{
$view = View::factory('crop/index');
$this->response->body($view);
}
public function action_do()
{
$view = View::factory('crop/do');
$error_message = NULL;
$filename = NULL;
if ($this->request->method() == Request::POST)
{
if (isset($_FILES['avatar']))
{
$filename = $this->_save_image($_FILES['avatar']);
}
}
if ( ! $filename)
{
$error_message = 'There was a problem while uploading the image.
Make sure it is uploaded and must be JPG/PNG/GIF file.';
}
$view->uploaded_file = $filename;
$view->error_message = $error_message;
$this->response->body($view);
}
protected function _save_image($image)
{
if (
! Upload::valid($image) OR
! Upload::not_empty($image) OR
! Upload::type($image, array('jpg', 'jpeg', 'png', 'gif')))
{
return FALSE;
}
$directory = DOCROOT.'uploads/';
if ($file = Upload::save($image, NULL, $directory))
{
$filename = strtolower(Text::random('alnum', 20)).'.jpg';
$img = Image::factory($file);
// Crop the image square half the height and crop from center
$new_height = (int) $img->height / 2;
$img->crop($new_height, $new_height)
->save($directory.$filename);
// Delete the temporary file
unlink($file);
return $filename;
}
return FALSE;
}
}
~~~
The `index` action displays the upload form whereas the `do` action will process the uploaded image and provides feedback to the user.
In `do` action, it checks if the request method was `POST`, then delegates the process to `_save_image()` method which in turn performs various checks and finally crops and saves the image to the `uploads` directory.
## Views
For the upload form (the `index` action), the view is located at `views/crop/index.php`.
~~~
<html>
<head>
<title>Upload Profile Image</title>
</head>
<body>
<h1>Upload your profile image</h1>
<form id="upload-form" action="<?php echo URL::site('crop/do') ?>" method="post" enctype="multipart/form-data">
<p>Choose file:</p>
<p><input type="file" name="avatar" id="avatar" /></p>
<p><input type="submit" name="submit" id="submit" value="Upload and crop" /></p>
</form>
</body>
</html>
~~~
View for `crop/do` action goes to `views/crop/do.php`.
~~~
<html>
<head>
<title>Upload Profile Image Result</title>
</head>
<body>
<?php if ($uploaded_file): ?>
<h1>Upload success</h1>
<p>
Here is your uploaded and cropped avatar:
<img src="<?php echo URL::site("/uploads/$uploaded_file") ?>" alt="Uploaded avatar" />
</p>
<?php else: ?>
<h1>Something went wrong with the upload</h1>
<p><?php echo $error_message ?></p>
<?php endif ?>
</body>
</html>
~~~
## Screenshots
Below are screenshots for this example.
![Original image](crop_orig.jpg)
_Original image to upload_
![Upload image form](crop_form.jpg)
_Upload image form_
![Upload result page](crop_result.jpg)
_Upload result form_

View File

@@ -0,0 +1,108 @@
# Dynamic Image Controller
In this example, we have images under `/uploads` under the webroot directory. We allow the user to render any image with dynamic dimension and is resized on the fly. It also caches the response for 1 hour to show basic caching mechanism.
## Route
First, we need a [Route]. This [Route] is based on this URL pattern:
`/imagefly/filename/width/height` - where filename is the name of the image without the extension. This assumes that all images are in `jpg` and all filenames uses numbers, letters, dash and underscores only.
This is our [Route] definition:
~~~
/**
* Set route for image fly
*/
Route::set('imagefly', 'imagefly/<image>/<width>/<height>', array('image' => '[-09a-zA-Z_]+', 'width' => '[0-9]+', 'height' => '[0-9]+'))
->defaults(array(
'controller' => 'imagefly',
'action' => 'index'
));
~~~
We ensure that the filename is only composed of letters, numbers and underscores, width and height must be numeric.
## Controller
Our controller simply accepts the request and capture the following parameters as defined by the [Route]:
* `filename` - without the filename extension (and without dot)
* `width`
* `height`
Then it finds the image file and when found, render it on the browser. Additional features added are browser caching.
~~~
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_Imagefly extends Controller {
public function action_index()
{
$file = $this->request->param('image');
$width = (int) $this->request->param('width');
$height = (int) $this->request->param('height');
$rendered = FALSE;
if ($file AND $width AND $height)
{
$filename = DOCROOT.'uploads/'.$file.'.jpg';
if (is_file($filename))
{
$this->_render_image($filename, $width, $height);
$rendered = TRUE;
}
}
if ( ! $rendered)
{
$this->response->status(404);
}
}
protected function _render_image($filename, $width, $height)
{
// Calculate ETag from original file padded with the dimension specs
$etag_sum = md5(base64_encode(file_get_contents($filename)).$width.','.$height);
// Render as image and cache for 1 hour
$this->response->headers('Content-Type', 'image/jpeg')
->headers('Cache-Control', 'max-age='.Date::HOUR.', public, must-revalidate')
->headers('Expires', gmdate('D, d M Y H:i:s', time() + Date::HOUR).' GMT')
->headers('Last-Modified', date('r', filemtime($filename)))
->headers('ETag', $etag_sum);
if (
$this->request->headers('if-none-match') AND
(string) $this->request->headers('if-none-match') === $etag_sum)
{
$this->response->status(304)
->headers('Content-Length', '0');
}
else
{
$result = Image::factory($filename)
->resize($width, $height)
->render('jpg');
$this->response->body($result);
}
}
}
~~~
When the parameters are invalid or the filename does not exists, it simply returns 404 not found error.
The rendering of image uses some caching mechanism. One by setting the max age and expire headers and second by using etags.
## Screenshots
Visiting [http://localhost/kohana/imagefly/kitteh/400/400](http://localhost/kohana/imagefly/kitteh/400/400) yields:
![Kitten 400x400](dynamic-400.jpg)
Visiting [http://localhost/kohana/imagefly/kitteh/600/500](http://localhost/kohana/imagefly/kitteh/600/500) yields:
![Kitten 400x400](dynamic-600.jpg)

View File

@@ -0,0 +1,139 @@
# Upload Image
The following example shows how to handle uploading of an image, resize it and save it to a file. Be sure you have enabled the [Image] module as discussed in getting started guide.
Assuming you are creating a web application that allows your members to upload their profile picture (avatar), the steps below explains it how.
## Controller
First we need to create a controller that handles the requests for uploading an image. We will name it `Controller_Avatar` and accessible through `/avatar` URL. Assuming that your project is located at [http://localhost/kohana](http://localhost/kohana), then our avatar controller is at [http://localhost/kohana/avatar](http://localhost/kohana/avatar).
For simplicity, the upload form will be on `index` action and `upload` action will process the uploaded file. This is what our controller now looks like. Please note that we are not using [Controller_Template], just [Controller].
~~~
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_Avatar extends Controller {
public function action_index()
{
$view = View::factory('avatar/index');
$this->response->body($view);
}
public function action_upload()
{
$view = View::factory('avatar/upload');
$error_message = NULL;
$filename = NULL;
if ($this->request->method() == Request::POST)
{
if (isset($_FILES['avatar']))
{
$filename = $this->_save_image($_FILES['avatar']);
}
}
if ( ! $filename)
{
$error_message = 'There was a problem while uploading the image.
Make sure it is uploaded and must be JPG/PNG/GIF file.';
}
$view->uploaded_file = $filename;
$view->error_message = $error_message;
$this->response->body($view);
}
protected function _save_image($image)
{
if (
! Upload::valid($image) OR
! Upload::not_empty($image) OR
! Upload::type($image, array('jpg', 'jpeg', 'png', 'gif')))
{
return FALSE;
}
$directory = DOCROOT.'uploads/';
if ($file = Upload::save($image, NULL, $directory))
{
$filename = strtolower(Text::random('alnum', 20)).'.jpg';
Image::factory($file)
->resize(200, 200, Image::AUTO)
->save($directory.$filename);
// Delete the temporary file
unlink($file);
return $filename;
}
return FALSE;
}
}
~~~
We have `index` and `upload` actions. `index` action will display the upload form and `upload` action will process the uploaded image and provides feedback to the user.
In `upload` action, it checks if the request method was `POST`, then delegates the process to `_save_image()` method which in turn performs various checks and finally resize and save the image to the `uploads` directory.
## Views
For the upload form (the `index` action), the view is located at `views/avatar/index.php`.
~~~
<html>
<head>
<title>Upload Avatar</title>
</head>
<body>
<h1>Upload your avatar</h1>
<form id="upload-form" action="<?php echo URL::site('avatar/upload') ?>" method="post" enctype="multipart/form-data">
<p>Choose file:</p>
<p><input type="file" name="avatar" id="avatar" /></p>
<p><input type="submit" name="submit" id="submit" value="Upload" /></p>
</form>
</body>
</html>
~~~
Take note of the action attribute. It points to our `avatar/upload` action whose view code goes to `views/avatar/upload.php`.
~~~
<html>
<head>
<title>Upload Avatar Result</title>
</head>
<body>
<?php if ($uploaded_file): ?>
<h1>Upload success</h1>
<p>
Here is your uploaded avatar:
<img src="<?php echo URL::site("/uploads/$uploaded_file") ?>" alt="Uploaded avatar" />
</p>
<?php else: ?>
<h1>Something went wrong with the upload</h1>
<p><?php echo $error_message ?></p>
<?php endif ?>
</body>
</html>
~~~
When the upload is successfull, a success message is displayed with the uploaded image displayed. Otherwise, when it fails, it displays an error message.
## Screenshots
Below are screenshots for this example.
![Upload image form](upload_form.jpg)
_Upload image form_
![Upload result page](upload_result.jpg)
_Upload result form_

View File

@@ -0,0 +1,21 @@
# Image
Kohana 3.x provides a simple yet powerful image manipulation module. The [Image] module provides features that allows your application to resize images, crop, rotate, flip and many more.
## Drivers
[Image] module ships with [Image_GD] driver which requires `GD` extension enabled in your PHP installation. This is the default driver. Additional drivers can be created by extending the [Image] class.
## Getting Started
Before using the image module, we must enable it first on `APPPATH/bootstrap.php`:
~~~
Kohana::modules(array(
...
'image' => MODPATH.'image', // Image manipulation
...
));
~~~
Next: [Using the image module](using).

View File

@@ -0,0 +1,6 @@
## [Image]()
- [Using](using)
- [Examples](examples)
- [Upload Image](examples/upload)
- [Crop Profile Image](examples/crop)
- [Dynamic Image Controller](examples/dynamic)

View File

@@ -0,0 +1,112 @@
# Basic Usage
Shown here are the basic usage of this module. For full documentation about the image module usage, visit the [Image] api browser.
## Creating Instance
[Image::factory()] creates an instance of the image object and prepares it for manipulation. It accepts the `filename` as an arguement and an optional `driver` parameter. When `driver` is not specified, the default driver `GD` is used.
~~~
// Uses the image from upload directory
$img = Image::factory(DOCROOT.'uploads/sample-image.jpg');
~~~
Once an instance is created, you can now manipulate the image by using the following instance methods.
## Resize
Resize the image to the given size. Either the width or the height can be omitted and the image will be resized proportionally.
Using the image object above, we can resize our image to say 150x150 pixels with automatic scaling using the code below:
~~~
$img->resize(150, 150, Image::AUTO);
~~~
The parameters are `width`, `height` and `master` dimension respectively. With `AUTO` master dimension, the image is resized by either width or height depending on which is closer to the specified dimension.
Other examples:
~~~
// Resize to 200 pixels on the shortest side
$img->resize(200, 200);
// Resize to 200x200 pixels, keeping aspect ratio
$img->resize(200, 200, Image::INVERSE);
// Resize to 500 pixel width, keeping aspect ratio
$img->resize(500, NULL);
// Resize to 500 pixel height, keeping aspect ratio
$img->resize(NULL, 500);
// Resize to 200x500 pixels, ignoring aspect ratio
$img->resize(200, 500, Image::NONE);
~~~
## Render
You can render the image object directly to the browser using the [Image::render()] method.
~~~
$img = Image::factory(DOCROOT.'uploads/colorado-farm-1920x1200.jpg');
header('Content-Type: image/jpeg');
echo $img->resize(300, 300)
->render();
~~~
What it did is resize a 1920x1200 wallpaper image into 300x300 proportionally and render it to the browser. If you are trying to render the image in a controller action, you can do instead:
~~~
$img = Image::factory(DOCROOT.'uploads/colorado-farm-1920x1200.jpg');
$this->response->headers('Content-Type', 'image/jpg');
$this->response->body(
$img->resize(300, 300)
->render()
);
~~~
[Image::render()] method also allows you to specify the type and quality of the rendered image.
~~~
// Render the image at 50% quality
$img->render(NULL, 50);
// Render the image as a PNG
$img->render('png');
~~~
## Save To File
[Image::save()] let's you save the image object to a file. It has two parameters: `filename` and `quality`. If `filename` is omitted, the original file used will be overwritten instead. The `quality` parameter is an integer from 1-100 which indicates the quality of image to save which defaults to 100.
On our example above, instead of rendering the file to the browser, you may want to save it somewhere instead. To do so, you may:
~~~
$img = Image::factory(DOCROOT.'uploads/colorado-farm-1920x1200.jpg');
$filename = DOCROOT.'uploads/img-'.uniqid().'.jpg';
$img->resize(300, 300)
->save($filename, 80);
~~~
What we do is resize the image and save it to file reducing quality to 80% and save it to the upload directory using a unique filename.
## Other Methods
There are more methods available for the [Image] module which provides powerfull features that are best describe in the API documentation. Here are some of them:
* [Image::background()] - Set the background color of an image.
* [Image::crop()] - Crop an image to the given size.
* [Image::flip()] - Flip the image along the horizontal or vertical axis.
* [Image::reflection()] - Add a reflection to an image.
* [Image::rotate()] - Rotate the image by a given amount.
* [Image::sharpen()] - Sharpen the image by a given amount.
* [Image::watermark()] - Add a watermark to an image with a specified opacity.
Next: [Examples](examples)

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,36 @@
<?php defined('SYSPATH') OR die('Kohana bootstrap needs to be included before tests run');
/**
* @package Kohana/Image
* @group kohana
* @group kohana.image
* @category Test
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://http://kohanaframework.org/license
*/
class Kohana_ImageTest extends PHPUnit_Framework_TestCase {
protected function setUp()
{
if ( ! extension_loaded('gd'))
{
$this->markTestSkipped('The GD extension is not available.');
}
}
/**
* Tests the Image::save() method for files that don't have extensions
*
* @return void
*/
public function test_save_without_extension()
{
$image = Image::factory(MODPATH.'image/tests/test_data/test_image');
$this->assertTrue($image->save(Kohana::$cache_dir.'/test_image'));
unlink(Kohana::$cache_dir.'/test_image');
}
} // End Kohana_ImageTest

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB