1
0
mirror of https://github.com/monstra-cms/monstra.git synced 2025-08-03 03:37:52 +02:00
Files
php-monstra/libraries/Gelato/Image/Image.php
2013-01-08 22:16:29 +02:00

656 lines
18 KiB
PHP

<?php
/**
* Gelato Library
*
* This source file is part of the Gelato Library. More information,
* documentation and tutorials can be found at http://gelato.monstra.org
*
* @package Gelato
*
* @author Romanenko Sergey / Awilum <awilum@msn.com>
* @copyright 2012-2013 Romanenko Sergey / Awilum <awilum@msn.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Image
{
/**
* Resizing contraint.
*
* @var integer
*/
const AUTO = 1;
/**
* Resizing contraint.
*
* @var integer
*/
const WIDTH = 2;
/**
* Resizing contraint.
*
* @var integer
*/
const HEIGHT = 3;
/**
* Watermark position.
*/
const TOP_LEFT = 4;
/**
* Watermark position.
*/
const TOP_RIGHT = 5;
/**
* Watermark position.
*/
const BOTTOM_LEFT = 6;
/**
* Watermark position.
*/
const BOTTOM_RIGHT = 7;
/**
* Watermark position.
*/
const CENTER = 8;
/**
* Holds info about the image.
*
* @var array
*/
protected $image_info;
/**
* Get value
*
* @param string $key Key
* @return mixed
*/
public function __get($key)
{
if (array_key_exists($key, $this->image_info)) return $this->image_info[$key];
}
/**
* Set value for specific key
*
* @param string $key Key
* @param mixed $value Value
*/
public function __set($key, $value)
{
$this->image_info[$key] = $value;
}
/**
* Image factory.
*
* <code>
* $image = Image::factory('original.png');
* </code>
*
* @param string $filename Filename
* @return Image
*/
public static function factory($filename)
{
return new Image($filename);
}
/**
* Construct
*
* @param string $file Filename
*/
public function __construct($file)
{
// Redefine vars
$file = (string) $file;
// Check if the file exists
if (file_exists($file)) {
// Extract attributes of the image file
list($this->width, $this->height, $type, $a) = getimagesize($file);
// Save image type
$this->type = $type;
// Create a new image
$this->image = $this->createImage($file, $type);
} else {
throw new RuntimeException(vsprintf("%s(): The file '{$file}' doesn't exist", array(__METHOD__)));
}
}
/**
* Create a new image from file.
*
* @param string $file Path to the image file
* @param integer $type Image type
* @return resource
*/
protected function createImage($file, $type)
{
// Create image from file
switch ($type) {
case IMAGETYPE_JPEG:
return imagecreatefromjpeg($file);
break;
case IMAGETYPE_GIF:
return imagecreatefromgif($file);
break;
case IMAGETYPE_PNG:
return imagecreatefrompng($file);
break;
default:
throw new RuntimeException(vsprintf("%s(): Unable to open '%s'. Unsupported image type.", array(__METHOD__, $type)));
}
}
/**
* Resizes the image to the chosen size.
*
* <code>
* Image::factory('original.png')->resize(800, 600)->save('edited.png');
* </code>
*
* @param integer $width Width of the image
* @param integer $height Height of the image
* @param integer $aspect_ratio Aspect ratio (Image::AUTO Image::WIDTH Image::HEIGHT)
* @return Image
*/
public function resize($width, $height = null, $aspect_ratio = null)
{
// Redefine vars
$width = (int) $width;
$height = ($height === null) ? null : (int) $height;
$aspect_ratio = ($aspect_ratio === null) ? null : (int) $aspect_ratio;
// Resizes the image to {$width}% of the original size
if ($height === null) {
$new_width = round($this->width * ($width / 100));
$new_height = round($this->height * ($width / 100));
} else {
// Resizes the image to the smalles possible dimension while maintaining aspect ratio
if ($aspect_ratio === Image::AUTO) {
// Calculate smallest size based on given height and width while maintaining aspect ratio
$percentage = min(($width / $this->width), ($height / $this->height));
$new_width = round($this->width * $percentage);
$new_height = round($this->height * $percentage);
// Resizes the image using the width to maintain aspect ratio
} elseif ($aspect_ratio === Image::WIDTH) {
// Base new size on given width while maintaining aspect ratio
$new_width = $width;
$new_height = round($this->height * ($width / $this->width));
// Resizes the image using the height to maintain aspect ratio
} elseif ($aspect_ratio === Image::HEIGHT) {
// Base new size on given height while maintaining aspect ratio
$new_width = round($this->width * ($height / $this->height));
$new_height = $height;
// Resizes the image to a dimension of {$width}x{$height} pixels while ignoring the aspect ratio
} else {
$new_width = $width;
$new_height = $height;
}
}
// Create a new true color image width new width and height
$resized = imagecreatetruecolor($new_width, $new_height);
// Copy and resize part of an image with resampling
imagecopyresampled($resized, $this->image, 0, 0, 0, 0, $new_width, $new_height, $this->width, $this->height);
// Destroy an image
imagedestroy($this->image);
// Create a new true color image width new width and height
$this->image = imagecreatetruecolor($new_width, $new_height);
// Copy and resize part of an image with resampling
imagecopyresampled($this->image, $resized, 0, 0, 0, 0, $new_width, $new_height, $new_width, $new_height);
// Destroy an image
imagedestroy($resized);
// Save new width and height
$this->width = $new_width;
$this->height = $new_height;
return $this;
}
/**
* Crops the image
*
* <code>
* Image::factory('original.png')->crop(800, 600, 0, 0)->save('edited.png');
* </code>
*
* @param integer $width Width of the crop
* @param integer $height Height of the crop
* @param integer $x The X coordinate of the cropped region's top left corner
* @param integer $y The Y coordinate of the cropped region's top left corner
* @return Image
*/
public function crop($width, $height, $x, $y)
{
// Redefine vars
$width = (int) $width;
$height = (int) $height;
$x = (int) $x;
$y = (int) $y;
// Calculate
if ($x + $width > $this->width) $width = $this->width - $x;
if ($y + $height > $this->height) $height = $this->height - $y;
if ($width <= 0 || $height <= 0) return false;
// Create a new true color image
$crop = imagecreatetruecolor($width, $height);
// Copy and resize part of an image with resampling
imagecopyresampled($crop, $this->image, 0, 0, $x, $y, $this->width, $this->height, $this->width, $this->height);
// Destroy an image
imagedestroy($this->image);
// Create a new true color image
$this->image = imagecreatetruecolor($width, $height);
// Copy and resize part of an image with resampling
imagecopyresampled($this->image, $crop, 0, 0, 0, 0, $width, $height, $width, $height);
// Destroy an image
imagedestroy($crop);
// Save new width and height
$this->width = $width;
$this->height = $height;
return $this;
}
/**
* Adds a watermark to the image.
*
* @param string $file Path to the image file
* @param integer $position Position of the watermark
* @param integer $opacity Opacity of the watermark in percent
* @return Image
*/
public function watermark($file, $position = null, $opacity = 100)
{
// Check if the image exists
if ( ! file_exists($file)) {
throw new RuntimeException(vsprintf("%s(): The image file ('%s') does not exist.", array(__METHOD__, $file)));
}
$watermark = $this->createImage($file, $this->type);
$watermarkW = imagesx($watermark);
$watermarkH = imagesy($watermark);
// Make sure that opacity is between 0 and 100
$opacity = max(min((int) $opacity, 100), 0);
if ($opacity < 100) {
if (GD_BUNDLED === 0) {
throw new RuntimeException(vsprintf("%s(): Setting watermark opacity requires the 'imagelayereffect' function which is only available in the bundled version of GD.", array(__METHOD__)));
}
// Convert alpha to 0-127
$alpha = min(round(abs(($opacity * 127 / 100) - 127)), 127);
$transparent = imagecolorallocatealpha($watermark, 0, 0, 0, $alpha);
imagelayereffect($watermark, IMG_EFFECT_OVERLAY);
imagefilledrectangle($watermark, 0, 0, $watermarkW, $watermarkH, $transparent);
}
// Position the watermark.
switch ($position) {
case Image::TOP_RIGHT:
$x = imagesx($this->image) - $watermarkW;
$y = 0;
break;
case Image::BOTTOM_LEFT:
$x = 0;
$y = imagesy($this->image) - $watermarkH;
break;
case Image::BOTTOM_RIGHT:
$x = imagesx($this->image) - $watermarkW;
$y = imagesy($this->image) - $watermarkH;
break;
case Image::CENTER:
$x = (imagesx($this->image) / 2) - ($watermarkW / 2);
$y = (imagesy($this->image) / 2) - ($watermarkH / 2);
break;
default:
$x = 0;
$y = 0;
}
imagealphablending($this->image, true);
imagecopy($this->image, $watermark, $x, $y, 0, 0, $watermarkW, $watermarkH);
imagedestroy($watermark);
// Return Image
return $this;
}
/**
* Convert image into grayscale
*
* <code>
* Image::factory('original.png')->grayscale()->save('edited.png');
* </code>
*
* @return Image
*/
public function grayscale()
{
imagefilter($this->image, IMG_FILTER_GRAYSCALE);
return $this;
}
/**
* Convert image into sepia
*
* <code>
* Image::factory('original.png')->sepia()->save('edited.png');
* </code>
*
* @return Image
*/
public function sepia()
{
imagefilter($this->image, IMG_FILTER_GRAYSCALE);
imagefilter($this->image, IMG_FILTER_COLORIZE, 112, 66, 20);
return $this;
}
/**
* Convert image into brightness
*
* <code>
* Image::factory('original.png')->brightness(60)->save('edited.png');
* </code>
*
* @param integer $level Level. From -255(min) to 255(max)
* @return Image
*/
public function brightness($level = 0)
{
imagefilter($this->image, IMG_FILTER_BRIGHTNESS, (int) $level);
return $this;
}
/**
* Convert image into colorize
*
* <code>
* Image::factory('original.png')->colorize(60, 0, 0)->save('edited.png');
* </code>
*
* @param integer $red Red
* @param integer $green Green
* @param integer $blue Blue
* @return Image
*/
public function colorize($red, $green, $blue)
{
imagefilter($this->image, IMG_FILTER_COLORIZE, (int) $red, (int) $green, (int) $blue);
return $this;
}
/**
* Convert image into contrast
*
* <code>
* Image::factory('original.png')->contrast(60)->save('edited.png');
* </code>
*
* @param integer $level Level. From -100(max) to 100(min) note the direction!
* @return Image
*/
public function contrast($level)
{
imagefilter($this->image, IMG_FILTER_CONTRAST, (int) $level);
return $this;
}
/**
* Creates a color based on a hex value.
*
* @param string $hex Hex code of the color
* @param integer $alpha Alpha. Default is 100
* @param boolean $returnRGB FALSE returns a color identifier, TRUE returns a RGB array
* @return integer
*/
protected function createColor($hex, $alpha = 100, $return_rgb = false)
{
// Redefine vars
$hex = (string) $hex;
$alpha = (int) $alpha;
$return_rgb = (bool) $return_rgb;
$hex = str_replace('#', '', $hex);
if (preg_match('/^([a-f0-9]{3}){1,2}$/i', $hex) === 0) {
throw new RuntimeException(vsprintf("%s(): Invalid color code ('%s').", array(__METHOD__, $hex)));
}
if (strlen($hex) === 3) {
$r = hexdec(str_repeat(substr($hex, 0, 1), 2));
$g = hexdec(str_repeat(substr($hex, 1, 1), 2));
$b = hexdec(str_repeat(substr($hex, 2, 1), 2));
} else {
$r = hexdec(substr($hex, 0, 2));
$g = hexdec(substr($hex, 2, 2));
$b = hexdec(substr($hex, 4, 2));
}
if ($return_rgb === true) {
return array('r' => $r, 'g' => $g, 'b' => $b);
} else {
// Convert alpha to 0-127
$alpha = min(round(abs(($alpha * 127 / 100) - 127)), 127);
return imagecolorallocatealpha($this->image, $r, $g, $b, $alpha);
}
}
/**
* Rotates the image using the given angle in degrees.
*
* <code>
* Image::factory('original.png')->rotate(90)->save('edited.png');
* </code>
*
* @param integer $degrees Degrees to rotate the image
* @return Image
*/
public function rotate($degrees)
{
if (GD_BUNDLED === 0) {
throw new RuntimeException(vsprintf("%s(): This method requires the 'imagerotate' function which is only available in the bundled version of GD.", array(__METHOD__)));
}
// Redefine vars
$degrees = (int) $degrees;
// Get image width and height
$width = imagesx($this->image);
$height = imagesy($this->image);
// Allocate a color for an image
$transparent = imagecolorallocatealpha($this->image, 0, 0, 0, 127);
// Rotate gif image
if ($this->image_info['type'] === IMAGETYPE_GIF) {
// Create a new true color image
$temp = imagecreatetruecolor($width, $height);
// Flood fill
imagefill($temp, 0, 0, $transparent);
// Copy part of an image
imagecopy($temp, $this->image, 0, 0, 0, 0, $width, $height);
// Destroy an image
imagedestroy($this->image);
// Save temp image
$this->image = $temp;
}
// Rotate an image with a given angle
$this->image = imagerotate($this->image, (360 - $degrees), $transparent);
// Define a color as transparent
imagecolortransparent($this->image, $transparent);
return $this;
}
/**
* Adds a border to the image.
*
* <code>
* Image::factory('original.png')->border('#000', 5)->save('edited.png');
* </code>
*
* @param string $color Hex code for the color
* @param integer $thickness Thickness of the frame in pixels
* @return Image
*/
public function border($color = '#000', $thickness = 5)
{
// Redefine vars
$color = (string) $color;
$thickness = (int) $thickness;
// Get image width and height
$width = imagesx($this->image);
$height = imagesy($this->image);
// Creates a color based on a hex value
$color = $this->createColor($color);
// Create border
for ($i = 0; $i < $thickness; $i++) {
if ($i < 0) {
$x = $width + 1;
$y = $hidth + 1;
} else {
$x = --$width;
$y = --$height;
}
imagerectangle($this->image, $i, $i, $x, $y, $color);
}
return $this;
}
/**
* Save image
*
* <code>
* Image::factory('original.png')->save('edited.png');
* </code>
*
* @param string $dest Desitination location of the file
* @param integer $quality Image quality. Default is 100
* @return Image
*/
public function save($file, $quality = 100)
{
// Redefine vars
$file = (string) $file;
$quality = (int) $quality;
$path_info = pathinfo($file);
if ( ! is_writable($path_info['dirname'])) {
throw new RuntimeException(vsprintf("%s(): '%s' is not writable.", array(__METHOD__, $path_info['dirname'])));
}
// Make sure that quality is between 0 and 100
$quality = max(min((int) $quality, 100), 0);
// Save image
switch ($path_info['extension']) {
case 'jpg':
case 'jpeg':
imagejpeg($this->image, $file, $quality);
break;
case 'gif':
imagegif($this->image, $file);
break;
case 'png':
imagealphablending($this->image, true);
imagesavealpha($this->image, true);
imagepng($this->image, $file, (9 - (round(($quality / 100) * 9))));
break;
default:
throw new RuntimeException(vsprintf("%s(): Unable to save to '%s'. Unsupported image format.", array(__METHOD__, $path_info['extension'])));
}
// Return Image
return $this;
}
/**
* Destructor
*/
public function __destruct()
{
imagedestroy($this->image);
}
}