Updated to KH 3.3 and improved

This commit is contained in:
Deon George
2013-04-13 16:17:56 +10:00
parent 6f50463ec7
commit 6f7913d363
1551 changed files with 96188 additions and 29813 deletions

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

@@ -1,4 +1,4 @@
<?php defined('SYSPATH') or die('No direct script access.');
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Image manipulation support. Allows images to be resized, cropped, etc.
*
@@ -16,6 +16,7 @@ abstract class Kohana_Image {
const HEIGHT = 0x03;
const AUTO = 0x04;
const INVERSE = 0x05;
const PRECISE = 0x06;
// Flipping directions
const HORIZONTAL = 0x11;
@@ -34,8 +35,8 @@ abstract class Kohana_Image {
*
* $image = Image::factory('upload/test.jpg');
*
* @param string image file path
* @param string driver type: GD, ImageMagick, etc
* @param string $file image file path
* @param string $driver driver type: GD, ImageMagick, etc
* @return Image
* @uses Image::$default_driver
*/
@@ -82,7 +83,7 @@ abstract class Kohana_Image {
* Loads information about the image. Will throw an exception if the image
* does not exist or is not an image.
*
* @param string image file path
* @param string $file image file path
* @return void
* @throws Kohana_Exception
*/
@@ -167,9 +168,9 @@ abstract class Kohana_Image {
* // Resize to 200x500 pixels, ignoring aspect ratio
* $image->resize(200, 500, Image::NONE);
*
* @param integer new width
* @param integer new height
* @param integer master dimension
* @param integer $width new width
* @param integer $height new height
* @param integer $master master dimension
* @return $this
* @uses Image::_do_resize
*/
@@ -247,6 +248,19 @@ abstract class Kohana_Image {
// 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
@@ -268,10 +282,10 @@ abstract class Kohana_Image {
* // Crop the image to 200x200 pixels, from the center
* $image->crop(200, 200);
*
* @param integer new width
* @param integer new height
* @param mixed offset from the left
* @param mixed offset from the top
* @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
*/
@@ -351,7 +365,7 @@ abstract class Kohana_Image {
* // Rotate 90% counter-clockwise
* $image->rotate(-90);
*
* @param integer degrees to rotate: -360-360
* @param integer $degrees degrees to rotate: -360-360
* @return $this
* @uses Image::_do_rotate
*/
@@ -367,7 +381,7 @@ abstract class Kohana_Image {
// Keep subtracting full circles until the degrees have normalized
$degrees -= 360;
}
while($degrees > 180);
while ($degrees > 180);
}
if ($degrees < -180)
@@ -377,7 +391,7 @@ abstract class Kohana_Image {
// Keep adding full circles until the degrees have normalized
$degrees += 360;
}
while($degrees < -180);
while ($degrees < -180);
}
$this->_do_rotate($degrees);
@@ -394,7 +408,7 @@ abstract class Kohana_Image {
* // Flip the image from left to right
* $image->flip(Image::VERTICAL);
*
* @param integer direction: Image::HORIZONTAL, Image::VERTICAL
* @param integer $direction direction: Image::HORIZONTAL, Image::VERTICAL
* @return $this
* @uses Image::_do_flip
*/
@@ -417,7 +431,7 @@ abstract class Kohana_Image {
* // Sharpen the image by 20%
* $image->sharpen(20);
*
* @param integer amount to sharpen: 1-100
* @param integer $amount amount to sharpen: 1-100
* @return $this
* @uses Image::_do_sharpen
*/
@@ -448,9 +462,9 @@ abstract class Kohana_Image {
* [!!] By default, the reflection will be go from transparent at the top
* to opaque at the bottom.
*
* @param integer reflection height
* @param integer reflection opacity: 0-100
* @param boolean TRUE to fade in, FALSE to fade out
* @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
*/
@@ -481,10 +495,10 @@ abstract class Kohana_Image {
* $mark = Image::factory('upload/watermark.png');
* $image->watermark($mark, TRUE, TRUE);
*
* @param object watermark Image instance
* @param integer offset from the left
* @param integer offset from the top
* @param integer opacity of watermark: 1-100
* @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
*/
@@ -540,8 +554,8 @@ abstract class Kohana_Image {
* // Make the image background black with 50% opacity
* $image->background('#000', 50);
*
* @param string hexadecimal color value
* @param integer background opacity: 0-100
* @param string $color hexadecimal color value
* @param integer $opacity background opacity: 0-100
* @return $this
* @uses Image::_do_background
*/
@@ -585,8 +599,8 @@ abstract class Kohana_Image {
* [!!] If the file does not exist, and the directory is not writable, an
* exception will be thrown.
*
* @param string new image path
* @param integer quality of image: 1-100
* @param string $file new image path
* @param integer $quality quality of image: 1-100
* @return boolean
* @uses Image::_save
* @throws Kohana_Exception
@@ -634,8 +648,8 @@ abstract class Kohana_Image {
* // Render the image as a PNG
* $data = $image->render('png');
*
* @param string image type to return: png, jpg, gif, etc
* @param integer quality of image: 1-100
* @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
*/
@@ -653,8 +667,8 @@ abstract class Kohana_Image {
/**
* Execute a resize.
*
* @param integer new width
* @param integer new height
* @param integer $width new width
* @param integer $height new height
* @return void
*/
abstract protected function _do_resize($width, $height);
@@ -662,10 +676,10 @@ abstract class Kohana_Image {
/**
* Execute a crop.
*
* @param integer new width
* @param integer new height
* @param integer offset from the left
* @param integer offset from the top
* @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);
@@ -673,7 +687,7 @@ abstract class Kohana_Image {
/**
* Execute a rotation.
*
* @param integer degrees to rotate
* @param integer $degrees degrees to rotate
* @return void
*/
abstract protected function _do_rotate($degrees);
@@ -681,7 +695,7 @@ abstract class Kohana_Image {
/**
* Execute a flip.
*
* @param integer direction to flip
* @param integer $direction direction to flip
* @return void
*/
abstract protected function _do_flip($direction);
@@ -689,7 +703,7 @@ abstract class Kohana_Image {
/**
* Execute a sharpen.
*
* @param integer amount to sharpen
* @param integer $amount amount to sharpen
* @return void
*/
abstract protected function _do_sharpen($amount);
@@ -697,9 +711,9 @@ abstract class Kohana_Image {
/**
* Execute a reflection.
*
* @param integer reflection height
* @param integer reflection opacity
* @param boolean TRUE to fade out, FALSE to fade in
* @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);
@@ -707,10 +721,10 @@ abstract class Kohana_Image {
/**
* Execute a watermarking.
*
* @param object watermarking Image
* @param integer offset from the left
* @param integer offset from the top
* @param integer opacity of watermark
* @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);
@@ -718,10 +732,10 @@ abstract class Kohana_Image {
/**
* Execute a background.
*
* @param integer red
* @param integer green
* @param integer blue
* @param integer opacity
* @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);
@@ -729,8 +743,8 @@ abstract class Kohana_Image {
/**
* Execute a save.
*
* @param string new image filename
* @param integer quality
* @param string $file new image filename
* @param integer $quality quality
* @return boolean
*/
abstract protected function _do_save($file, $quality);
@@ -738,8 +752,8 @@ abstract class Kohana_Image {
/**
* Execute a render.
*
* @param string image type: png, jpg, gif, etc
* @param integer quality
* @param string $type image type: png, jpg, gif, etc
* @param integer $quality quality
* @return string
*/
abstract protected function _do_render($type, $quality);

View File

@@ -1,4 +1,4 @@
<?php defined('SYSPATH') or die('No direct script access.');
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* Support for image manipulation using [GD](http://php.net/GD).
*
@@ -76,6 +76,7 @@ class Kohana_Image_GD extends Image {
/**
* Runs [Image_GD::check] and loads the image.
*
* @param string $file image file path
* @return void
* @throws Kohana_Exception
*/
@@ -150,6 +151,13 @@ class Kohana_Image_GD extends Image {
}
}
/**
* 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
@@ -200,6 +208,15 @@ class Kohana_Image_GD extends 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
@@ -221,6 +238,12 @@ class Kohana_Image_GD extends Image {
}
}
/**
* Execute a rotation.
*
* @param integer $degrees degrees to rotate
* @return void
*/
protected function _do_rotate($degrees)
{
if ( ! Image_GD::$_bundled)
@@ -257,6 +280,12 @@ class Kohana_Image_GD extends Image {
}
}
/**
* Execute a flip.
*
* @param integer $direction direction to flip
* @return void
*/
protected function _do_flip($direction)
{
// Create the flipped image
@@ -291,6 +320,12 @@ class Kohana_Image_GD extends Image {
$this->height = imagesy($flipped);
}
/**
* Execute a sharpen.
*
* @param integer $amount amount to sharpen
* @return void
*/
protected function _do_sharpen($amount)
{
if ( ! Image_GD::$_bundled)
@@ -322,6 +357,14 @@ class Kohana_Image_GD extends 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)
@@ -394,6 +437,15 @@ class Kohana_Image_GD extends Image {
$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)
@@ -439,6 +491,15 @@ class Kohana_Image_GD extends Image {
}
}
/**
* 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
@@ -468,6 +529,13 @@ class Kohana_Image_GD extends Image {
}
}
/**
* 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
@@ -492,6 +560,13 @@ class Kohana_Image_GD extends Image {
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
@@ -520,13 +595,19 @@ class Kohana_Image_GD extends Image {
* Get the GD saving function and image type for this extension.
* Also normalizes the quality setting
*
* @param string image type: png, jpg, etc
* @param integer image quality
* @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':
@@ -563,8 +644,8 @@ class Kohana_Image_GD extends Image {
/**
* Create an empty image with the given width and height.
*
* @param integer image width
* @param integer image height
* @param integer $width image width
* @param integer $height image height
* @return resource
*/
protected function _create($width, $height)

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

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

View File

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

View File

@@ -1,4 +1,4 @@
<?php defined('SYSPATH') or die('No direct script access.');
<?php defined('SYSPATH') OR die('No direct script access.');
return array(
// Leave this alone
@@ -17,7 +17,7 @@ return array(
'description' => 'Image manipulation.',
// Copyright message, shown in the footer for this module
'copyright' => '&copy; 20082010 Kohana Team',
'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

@@ -1,3 +1,6 @@
## [Image]()
- [Using](using)
- [Examples](examples)
- [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.

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