123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781 |
- <?php
- /**
- * Image manipulation support. Allows images to be resized, cropped, etc.
- *
- * @package Kohana/Image
- * @category Base
- * @author Kohana Team
- * @copyright (c) Kohana Team
- * @license https://koseven.ga/LICENSE.md
- */
- 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;
- // PHP image_type_to_mime_type doesn't know WEBP yet
- const IMAGETYPE_WEBP = -1;
- /**
- * @deprecated - provide an image.default_driver value in your configuration instead
- * @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 driver from configuration file or default one
- $configured_driver = Kohana::$config->load('image.default_driver');
- $driver = ($configured_driver) ? $configured_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',
- [':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',
- [':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',
- [':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);
- }
- /**
- * Returns the image mime type
- * Adds support for webp image type, which is not known by php
- *
- * @param string $type image type: png, jpg, gif, etc
- * @return string
- */
- protected function image_type_to_mime_type($type)
- {
- if ($type === self::IMAGETYPE_WEBP)
- return 'image/webp';
- return image_type_to_mime_type($type);
- }
- /**
- * 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);
- }
|