Kohana v3.3.0
0
modules/image/README.markdown
Normal file
3
modules/image/classes/Image.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') OR die('No direct script access.');
|
||||
|
||||
abstract class Image extends Kohana_Image {}
|
3
modules/image/classes/Image/GD.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') OR die('No direct script access.');
|
||||
|
||||
class Image_GD extends Kohana_Image_GD {}
|
3
modules/image/classes/Image/Imagick.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') OR die('No direct script access.');
|
||||
|
||||
class Image_Imagick extends Kohana_Image_Imagick {}
|
761
modules/image/classes/Kohana/Image.php
Normal 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
|
665
modules/image/classes/Kohana/Image/GD.php
Normal 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
|
334
modules/image/classes/Kohana/Image/Imagick.php
Normal 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
|
23
modules/image/config/userguide.php
Normal 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' => '© 2008–2012 Kohana Team',
|
||||
)
|
||||
)
|
||||
);
|
7
modules/image/guide/image/examples.md
Normal 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)
|
141
modules/image/guide/image/examples/crop.md
Normal 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 to upload_
|
||||
|
||||

|
||||
|
||||
_Upload image form_
|
||||
|
||||

|
||||
|
||||
_Upload result form_
|
108
modules/image/guide/image/examples/dynamic.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
Visiting [http://localhost/kohana/imagefly/kitteh/600/500](http://localhost/kohana/imagefly/kitteh/600/500) yields:
|
||||
|
||||

|
139
modules/image/guide/image/examples/upload.md
Normal 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 result form_
|
21
modules/image/guide/image/index.md
Normal 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).
|
6
modules/image/guide/image/menu.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## [Image]()
|
||||
- [Using](using)
|
||||
- [Examples](examples)
|
||||
- [Upload Image](examples/upload)
|
||||
- [Crop Profile Image](examples/crop)
|
||||
- [Dynamic Image Controller](examples/dynamic)
|
112
modules/image/guide/image/using.md
Normal 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)
|
BIN
modules/image/media/guide/image/Thumbs.db
Normal file
BIN
modules/image/media/guide/image/crop_form.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
modules/image/media/guide/image/crop_orig.jpg
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
modules/image/media/guide/image/crop_result.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
modules/image/media/guide/image/dynamic-400.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
modules/image/media/guide/image/dynamic-600.jpg
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
modules/image/media/guide/image/upload_form.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
modules/image/media/guide/image/upload_result.jpg
Normal file
After Width: | Height: | Size: 18 KiB |
36
modules/image/tests/kohana/ImageTest.php
Normal 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
|
BIN
modules/image/tests/test_data/test_image
Normal file
After Width: | Height: | Size: 3.4 KiB |