/modules/image/classes/Kohana/Image.php
PHP | 761 lines | 319 code | 83 blank | 359 comment | 34 complexity | 4376fb3d86668b1b1295cc74800e15d1 MD5 | raw file
Possible License(s): LGPL-2.1, MIT, BSD-3-Clause
- <?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