1417 lines
37 KiB
PHP
1417 lines
37 KiB
PHP
<?php
|
|
|
|
// $Id: mImage.class.php 395 2008-05-28 19:41:06Z dakl $
|
|
|
|
/**
|
|
* Class for image manipulations
|
|
*
|
|
* PHP version 5
|
|
*
|
|
* Creative Commons
|
|
* Commons Deed
|
|
* Attribution 2.5 Poland
|
|
* You are free:
|
|
* * to copy, distribute, display, and perform the work
|
|
* * to make derivative works
|
|
* * to make commercial use of the work
|
|
* Under the following conditions:
|
|
* by Attribution. You must attribute the work in the manner specified by the
|
|
* author or licensor.
|
|
* * For any reuse or distribution, you must make clear to others the license
|
|
* terms of this work.
|
|
* * Any of these conditions can be waived if you get permission from the
|
|
* copyright holder.
|
|
* Your fair use and other rights are in no way affected by the above.
|
|
*
|
|
* This is a human-readable summary of the Legal Code
|
|
* (http://creativecommons.org/licenses/by/2.5/pl/legalcode)
|
|
*
|
|
* @category Classes
|
|
* @package mimage
|
|
* @author Marcin Sztolcman <marcin /at/ urzenia /dot/ net>
|
|
* @copyright 2006 Marcin Sztolcman
|
|
* @license http://creativecommons.org/licenses/by/2.5/pl/deed.en
|
|
* @version SVN: $Id: mimage.class.php 395 2008-05-28 19:41:06Z dakl $
|
|
* @link http://repo.urzenia.net/PHP:Class_mImage
|
|
* @link $HeadURL: http://svn.urzenia.net/repo/trunk/php/class_mimage.php $
|
|
*/
|
|
|
|
/**
|
|
* Class for image manipulations
|
|
*
|
|
* Scaling, resizing, cropping, applying filters and many others.
|
|
*
|
|
* Error codes:
|
|
* 140 - InvalidArgumentException : Cannot read "%s"
|
|
* 141 - InvalidArgumentException : Invalid filter: "%s".
|
|
* 142 - InvalidArgumentException : Attribute "%s" is read only.
|
|
* 143 - InvalidArgumentException : Both values: $max_width and $max_height cannot be false
|
|
* 144 - InvalidArgumentException : Incorrect quant of elements in "$padding" parameter.
|
|
* 145 - InvalidArgumentException : Attribute "%s" doesn't exists.
|
|
* 100 - LogicException : Image no loaded - call mImage::open() first.
|
|
* 250 - UnexpectedValueException : Specified image (%s) has dimensions different then %dx%d.
|
|
* 200 - RuntimeException : Cannot write to "%s" directory.
|
|
* 201 - RuntimeException : File "%s" already exists.
|
|
* 120 - BadMethodCallException : Incorrect function imagerotate.
|
|
*
|
|
* @category Classes
|
|
* @package mimage
|
|
* @author Marcin Sztolcman <marcin /at/ urzenia /dot/ net>
|
|
* @copyright 2006 Marcin Sztolcman
|
|
* @license http://creativecommons.org/licenses/by/2.5/pl/deed.en
|
|
* @version SVN: $Id: mimage.class.php 395 2008-05-28 19:41:06Z dakl $
|
|
* @link http://repo.urzenia.net/PHP:Class_mImage
|
|
* @link $HeadURL: http://svn.urzenia.net/repo/trunk/php/class_mimage.php $
|
|
*/
|
|
class mImage {
|
|
|
|
/**
|
|
* Constant - default output format.
|
|
*/
|
|
const FORMAT = 'png';
|
|
/**
|
|
* Constant - default jpeg quality.
|
|
*/
|
|
const QUALITY = 75;
|
|
|
|
/**
|
|
* Constant - max size of image. Used when in {scale,grow,shrink}Prop
|
|
* as $max_{width,height} is false.
|
|
*/
|
|
const MAX_SIZE = 40960;
|
|
|
|
/**
|
|
* Properties of image
|
|
*
|
|
* Have to be read only, so we keep it in internal array and
|
|
* access to them is via __set() and __get() methods
|
|
*
|
|
* @var array
|
|
* @access protected
|
|
*/
|
|
protected $properties = array(
|
|
'width' => null,
|
|
'height' => null,
|
|
'truecolor' => null
|
|
);
|
|
|
|
/**
|
|
* Image descriptor (handler)
|
|
*
|
|
* @var object
|
|
* @access protected
|
|
*/
|
|
protected $body = null;
|
|
|
|
/**
|
|
* Available filters
|
|
*
|
|
* List is initialized from constructor, because filters are available
|
|
* only if php use it's internal gd.
|
|
*
|
|
* @var array
|
|
* @access protected
|
|
* @static
|
|
*/
|
|
protected static $filters;
|
|
|
|
/**
|
|
* Image formats handled by this class.
|
|
*
|
|
* This array maps possible formats to assigned with them functions from
|
|
* image extension.
|
|
*
|
|
* @var array
|
|
* @access protected
|
|
* @static
|
|
*/
|
|
protected static $formats = array(
|
|
'jpg' => 'imagejpeg',
|
|
'jpeg' => 'imagejpeg',
|
|
'gif' => 'imagegif',
|
|
'png' => 'imagepng'
|
|
);
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* If filename in argument is specfified, it opens file and read them.
|
|
* Assign available filters too (if any).
|
|
*
|
|
* Params description: {@link mImage::open()}
|
|
*
|
|
* @param string $fname
|
|
* @param mixed $width
|
|
* @param mixed $height
|
|
* @param boolean $exact
|
|
*
|
|
* @access public
|
|
*/
|
|
public function __construct($fname=null, $width=null, $height=null,
|
|
$exact=true)
|
|
{
|
|
// these constants aren't compiled in if php use external gd.
|
|
// In this case, we cant put here these constant, only
|
|
// if imagefilter() function exists (it means: when php use bundled
|
|
// version of gd)
|
|
if (function_exists('imagefilter') && !self::$filters) {
|
|
self::$filters = array(
|
|
'negate' => IMG_FILTER_NEGATE,
|
|
'grayscale' => IMG_FILTER_GRAYSCALE,
|
|
'brightness' => IMG_FILTER_BRIGHTNESS,
|
|
'contrast' => IMG_FILTER_CONTRAST,
|
|
'colorize' => IMG_FILTER_COLORIZE,
|
|
'edge' => IMG_FILTER_EDGEDETECT,
|
|
'emboss' => IMG_FILTER_EMBOSS,
|
|
'gaussian' => IMG_FILTER_GAUSSIAN_BLUR,
|
|
'blur' => IMG_FILTER_SELECTIVE_BLUR,
|
|
'sketchy' => IMG_FILTER_MEAN_REMOVAL,
|
|
'smooth' => IMG_FILTER_SMOOTH
|
|
);
|
|
}
|
|
|
|
if (!is_null($fname)) {
|
|
$this->open($fname, $width, $height, $exact);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Destructor
|
|
*
|
|
* Destroy opened image
|
|
*
|
|
* @access public
|
|
*/
|
|
public function __destruct()
|
|
{
|
|
if (is_object($this->body)) {
|
|
imagedestroy($this->body);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read file and assign properties.
|
|
*
|
|
* If $width and $height are not null, and $exact:
|
|
* - is true: if image dimensions are other then $width x $height, raise
|
|
* UnexpectedValueException
|
|
* - is false: propportional scale image to specified dimensions.
|
|
*
|
|
* $width or $height can be false ({@link mImage::scaleProp() more}).
|
|
*
|
|
* @param string $fname filename to read
|
|
* @param mixed $width
|
|
* @param mixed $height
|
|
* @param boolean $exact
|
|
*
|
|
* @return mixed
|
|
* @throws InvalidArgumentException
|
|
* @throws UnexpectedValueException
|
|
* @access public
|
|
*/
|
|
public function open($fname, $width=null, $height=null, $exact=true)
|
|
{
|
|
$fname = realpath($fname);
|
|
if (empty($fname) || !file_exists($fname) || !is_readable($fname)) {
|
|
throw new InvalidArgumentException(sprintf('Cannot read "%s".', $fname), 140);
|
|
}
|
|
|
|
$dst = imagecreatefromstring(file_get_contents($fname));
|
|
$this->swap($dst);
|
|
|
|
if (!is_null($width) && !is_null($height)) {
|
|
if (false === $width && false === $height) {
|
|
throw new InvalidArgumentException('Both values: $max_width and $max_height ' .
|
|
'cannot be false', 143);
|
|
}
|
|
|
|
if ($exact) { // if image has other dimensions then specified
|
|
if (
|
|
( false !== $width && $width != $this->width ) ||
|
|
( false !== $height && $height != $this->height )
|
|
) {
|
|
|
|
throw new UnexpectedValueException(sprintf('Specified image (%s) has ' .
|
|
'dimensions different then %dx%d.',
|
|
|
|
$fname,
|
|
$width,
|
|
$height
|
|
), 250);
|
|
}
|
|
} else {
|
|
return $this->scaleProp($width, $height, 'ffffff', false, 'c', 0);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Send image to standard output.
|
|
*
|
|
* @param string $format possible values in self::$formats
|
|
* @param integer $quality quality of output jpeg
|
|
* @param boolean $sendHeaders if true, send content-type header
|
|
*
|
|
* @access public
|
|
*/
|
|
public function show($format=null, $quality=null, $sendHeaders=true)
|
|
{
|
|
$this->isInitialized();
|
|
|
|
$format = $this->checkFormat($format);
|
|
|
|
if ($sendHeaders) {
|
|
header('Content-type: image/' . $format);
|
|
}
|
|
|
|
if (is_null($quality)) {
|
|
$quality = self::QUALITY;
|
|
}
|
|
|
|
$fun = self::$formats[$format];
|
|
$fun($this->body, '', $quality);
|
|
}
|
|
|
|
/**
|
|
* Save image to given file.
|
|
*
|
|
* @param string $fname filename
|
|
* @param string $format possible values in self::$formats
|
|
* @param integer $quality quality of output jpeg
|
|
* @param boolean $overwrite if true, it overwrite file if it exists
|
|
*
|
|
* @throws RuntimeException
|
|
* @access public
|
|
*/
|
|
public function saveToFile($fname, $format=null, $quality=null, $overwrite=false)
|
|
{
|
|
$this->isInitialized();
|
|
|
|
$pathinfo = pathinfo($fname);
|
|
|
|
if (is_null($format)) {
|
|
$format = $pathinfo['extension'];
|
|
}
|
|
$format = $this->checkFormat(strtolower($format));
|
|
|
|
if ('' == $pathinfo['dirname']) {
|
|
$pathinfo['dirname'] = '.';
|
|
}
|
|
$pathinfo['dirname'] = realpath($pathinfo['dirname']);
|
|
$fullpath = $pathinfo['dirname'] . DIRECTORY_SEPARATOR . $pathinfo['basename'];
|
|
|
|
if (!is_writeable($pathinfo['dirname'])) {
|
|
throw new RuntimeException(sprintf('Cannot write to "%s" directory.', $pathinfo['dirname']), 200);
|
|
}
|
|
if (file_exists($fullpath)) {
|
|
if ($overwrite) {
|
|
unlink($fullpath);
|
|
} else {
|
|
throw new RuntimeException(sprintf('File "%s" already exists.', $fullpath), 201);
|
|
}
|
|
}
|
|
|
|
if (is_null($quality)) {
|
|
$quality = self::QUALITY;
|
|
}
|
|
|
|
$fun = self::$formats[$format];
|
|
$fun($this->body, $fname, $quality);
|
|
}
|
|
|
|
/**
|
|
* Return image as an string.
|
|
*
|
|
* @param string $format possible alues in self::$formats
|
|
* @param integer $quality quality of output jpeg
|
|
*
|
|
* @return string
|
|
* @access public
|
|
*/
|
|
public function toString($format=null, $quality=null)
|
|
{
|
|
$format = $this->checkFormat($format);
|
|
|
|
ob_start();
|
|
$fun = self::$formats[$format];
|
|
$fun($this->body, '', $quality);
|
|
return ob_get_flush();
|
|
}
|
|
|
|
/**
|
|
* Proportional scaling of image
|
|
*
|
|
* If $fill == true, image dimensions has been equal to $max_{width,height}.
|
|
* Free space between scaled image and edge of image will be filled by
|
|
* $bgcolor.
|
|
* If $fill == false, image dimensions will be equal to scaled image, not
|
|
* bigger then $max_{width,height}.
|
|
*
|
|
* If $padding is specified, image will be scaled down to be equal
|
|
* or smaller then $max_width-$padding and $max_height-$padding, and free
|
|
* space will be filled by $bgcolor.
|
|
* $padding can be either an integer (all padding are equal) or array:
|
|
* - 2 elements for top/bottom and left/right paddings;
|
|
* - 4 elemnts for top, tight, bottm and left values of padding
|
|
* Scaled image wil be 'placed' into selected (in $position) place of
|
|
* output image.
|
|
*
|
|
* $max_width or $max_height (but no both) can be false. In this case image
|
|
* will be scaled only for second, non-false dimension.
|
|
*
|
|
* Return false if fail.
|
|
*
|
|
* @param mixed $max_width
|
|
* @param mixed $max_height
|
|
* @param string $bgcolor
|
|
* @param boolean $fill
|
|
* @param string $position
|
|
* @param mixed $padding
|
|
*
|
|
* @return boolean
|
|
* @throws InvalidArgumentException
|
|
* @access public
|
|
*/
|
|
public function scaleProp($max_width, $max_height, $bgcolor='ffffff',
|
|
$fill=true, $position='c', $padding=0)
|
|
{
|
|
$this->isInitialized();
|
|
|
|
if ($max_width === false && $max_height === false) {
|
|
throw new InvalidArgumentException('Both values: $max_width and $max_height cannot be false', 143);
|
|
}
|
|
if ($max_width === false) {
|
|
$max_width = self::MAX_SIZE;
|
|
$fill = false;
|
|
}
|
|
if ($max_height === false) {
|
|
$max_height = self::MAX_SIZE;
|
|
$fill = false;
|
|
}
|
|
|
|
if (!is_array($padding)) {
|
|
$padding = array_fill(0, 4, $padding);
|
|
} elseif (2 == count($padding)) {
|
|
$padding = array($padding[0], $padding[1], $padding[0], $padding[1]);
|
|
} elseif (4 != count($padding)) {
|
|
throw new InvalidArgumentException('Incorrect quant of elements in "$padding" parameter.', 144);
|
|
}
|
|
|
|
$width = $max_width - ($padding[1] + $padding[3]);
|
|
$height = $max_height - ($padding[0] + $padding[2]);
|
|
|
|
list($th_width, $th_height) = $this->calculateSize($width, $height);
|
|
|
|
if ($fill) {
|
|
$dst_width = $max_width;
|
|
$dst_height = $max_height;
|
|
|
|
$pos = $this->calculatePosition($width, $height, $position);
|
|
} else {
|
|
$dst_width = $th_width + ($padding[1] + $padding[3]);
|
|
$dst_height = $th_height + ($padding[0] + $padding[2]);
|
|
|
|
$pos = $this->calculatePosition($th_width, $th_height, $position);
|
|
}
|
|
|
|
// initializing destination image
|
|
$dst = $this->newImage($dst_width, $dst_height);
|
|
|
|
imagefill($dst, 0, 0, $this->color($bgcolor)); //set background color
|
|
$test = imagecopyresampled($dst, $this->body,
|
|
$pos[0]+$padding[3], $pos[1]+$padding[0],
|
|
$pos[2], $pos[3],
|
|
$th_width, $th_height,
|
|
$this->properties['width'], $this->properties['height']
|
|
);
|
|
|
|
if ($test) {
|
|
return $this->swap($dst);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Non proportional scaling
|
|
*
|
|
* If $padding is specified, image will be scaled down to be equal
|
|
* or smaller then $max_width-$padding and $max_height-$padding, and free
|
|
* space will be filled by $bgcolor.
|
|
* $padding can be either an integer (all padding are equal) or array of
|
|
* top, right, bottom, and left padding value.
|
|
*
|
|
* Return false if fail
|
|
*
|
|
* @param integer $width width of image
|
|
* @param integer $height height of image
|
|
* @param mixed $padding
|
|
* @param string $bgcolor background color
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function scaleNonProp($width, $height, $padding=0, $bgcolor='ffffff')
|
|
{
|
|
$this->isInitialized();
|
|
|
|
$dst = $this->newImage($width, $height);
|
|
if ($padding) {
|
|
imagefill($dst, 0, 0, $this->color($bgcolor));
|
|
}
|
|
if (!is_array($padding)) {
|
|
$padding = array_fill(0, 4, $padding);
|
|
}
|
|
|
|
$test = imagecopyresampled($dst, $this->body,
|
|
$padding[3], $padding[0], 0, 0,
|
|
$width - ($padding[1] + $padding[3]),
|
|
$height - ($padding[0] + $padding[2]),
|
|
$this->properties['width'], $this->properties['height']
|
|
);
|
|
|
|
if ($test) {
|
|
return $this->swap($dst);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Grow an image (proportional)
|
|
*
|
|
* Grow image if is smaller then $max_width & $max_height. Return true if
|
|
* bigger and leave image without modifying it.
|
|
* Use mImage::scaleProp()
|
|
*
|
|
* $max_width or $max_height (but no both) can be false. In this case image
|
|
* will be scaled only for second, non-false dimension.
|
|
*
|
|
* Params description: {@link mImage::scaleProp()}
|
|
*
|
|
* @param integer $max_width
|
|
* @param integer $max_height
|
|
* @param string $bgcolor
|
|
* @param boolean $fill
|
|
* @param string $position
|
|
* @param mixed $padding
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function growProp($max_width, $max_height, $bgcolor='ffffff',
|
|
$fill=true, $position='c', $padding=0)
|
|
{
|
|
if ($max_width === false && $max_height === false) {
|
|
throw new InvalidArgumentException('Both values: $max_width and $max_height cannot be false', 143);
|
|
}
|
|
if (
|
|
($max_width !== false && $this->properties['width'] <= $max_width) ||
|
|
($max_height !== false && $this->properties['height'] <= $max_height)
|
|
) {
|
|
return $this->scaleProp($max_width, $max_height, $bgcolor,
|
|
$fill, $position, $padding);
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Grow an image (non-proportional)
|
|
*
|
|
* Grow image if is smaller then $max_width & $max_height. Return true if
|
|
* bigger and leave image non touched.
|
|
* Use {@link mImage::scaleNonProp()}
|
|
*
|
|
* Params description: {@link mImage::scaleProp()}
|
|
*
|
|
* @param integer $max_width
|
|
* @param integer $max_height
|
|
* @param string $bgcolor
|
|
* @param boolean $fill
|
|
* @param string $position
|
|
* @param mixed $padding
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function growNonProp($max_width, $max_height,
|
|
$padding=0, $bgcolor='ffffff')
|
|
{
|
|
if ($this->properties['width'] <= $max_width ||
|
|
$this->properties['height'] <= $max_height) {
|
|
return $this->scaleNonProp($max_width, $max_height, $bgcolor,
|
|
$fill, $position, $padding);
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shrink an image (proportional)
|
|
*
|
|
* Shrinks image if is bigger then $min_width & $min_height. Return true if
|
|
* smaller and leave image non touched.
|
|
* Use mImage::scaleProp()
|
|
*
|
|
* Params description: {@link mImage::scaleProp()}
|
|
*
|
|
* @param integer $min_width
|
|
* @param integer $min_height
|
|
* @param string $bgcolor
|
|
* @param boolean $fill
|
|
* @param string $position
|
|
* @param mixed $padding
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function shrinkProp($min_width, $min_height, $bgcolor='ffffff',
|
|
$fill=true, $position='c', $padding=0)
|
|
{
|
|
if ($min_width === false && $min_height === false) {
|
|
throw new InvalidArgumentException('Both values: $min_width and $min_height cannot be false', 143);
|
|
}
|
|
if (
|
|
($min_width !== false && $this->properties['width'] >= $min_width) ||
|
|
($min_height !== false && $this->properties['height'] >= $min_height)
|
|
) {
|
|
return $this->scaleProp($min_width, $min_height, $bgcolor,
|
|
$fill, $position, $padding);
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shrink an image (non-proportional)
|
|
*
|
|
* Shrinks image if is bigger then $min_width & $min_height. Return true if
|
|
* smaller and leave image non touched.
|
|
* Use mImage::scaleNonProp()
|
|
*
|
|
* Params description: {@link mImage::scaleProp()}
|
|
*
|
|
* @param integer $min_width
|
|
* @param integer $min_height
|
|
* @param string $bgcolor
|
|
* @param boolean $fill
|
|
* @param string $position
|
|
* @param mixed $padding
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function shrinkNonProp($min_width, $min_height,
|
|
$padding=0, $bgcolor='ffffff')
|
|
{
|
|
if ($this->properties['width'] >= $min_width ||
|
|
$this->properties['height'] >= $min_height) {
|
|
return $this->scaleNonProp($min_width, $min_height,
|
|
$padding, $bgcolor);
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cropping of an image, method 1.
|
|
*
|
|
* As parameters are given: x and y coordinats of top left corner
|
|
* cropped area, and width and height of it, or 4-elements array
|
|
* with the same data as $x.
|
|
*
|
|
* @param integer $x
|
|
* @param integer $y
|
|
* @param integer $h
|
|
* @param integer $w
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function crop1($x, $y=null, $w=null, $h=null)
|
|
{
|
|
$this->isInitialized();
|
|
|
|
if (is_array($x)) {
|
|
$y = $x[1];
|
|
$h = $x[2];
|
|
$w = $x[3];
|
|
$x = $x[0];
|
|
}
|
|
|
|
$dst = $this->newImage($w, $h);
|
|
|
|
$test = imagecopy($dst, $this->body, 0, 0, $x, $y, $w, $h);
|
|
|
|
if ($test) {
|
|
return $this->swap($dst);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cropping of an image, method 2.
|
|
*
|
|
* As parameters are given: top, right, bottom and left coordinates of
|
|
* image, or 4-elements array with the same data as $t.
|
|
*
|
|
* @param integer $t
|
|
* @param integer $r
|
|
* @param integer $b
|
|
* @param integer $l
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function crop2($t, $r=null, $b=null, $l=null)
|
|
{
|
|
if (is_array($t)) {
|
|
$r = $t[1];
|
|
$b = $t[2];
|
|
$l = $t[3];
|
|
$t = $t[0];
|
|
}
|
|
|
|
return $this->crop1($t, $l, $r - $l, $b - $t);
|
|
}
|
|
|
|
/**
|
|
* Apply filter to an image
|
|
*
|
|
* @param string $filter filter name (available filters from self::$filters)
|
|
* @param string $filter,... {@link http://php.net/imagefilter description}
|
|
*
|
|
* @return boolean
|
|
* @throws InvalidArgumentException if invalid filter name
|
|
* @access public
|
|
*/
|
|
public function filter($filter)
|
|
{
|
|
$this->isInitialized();
|
|
|
|
if (!in_array($filter, self::$filters)) {
|
|
throw new InvalidArgumentException(sprintf('Invalid filter: "%s".', $filter), 141);
|
|
}
|
|
|
|
//not tested yet - if fail, remove line below
|
|
$argv = func_get_args();
|
|
array_shift($argv);
|
|
return call_user_func_array('imagefilter',
|
|
array_merge(array($this->body, self::$filters[$filter]),
|
|
$argv)
|
|
);
|
|
|
|
// Workaround: i don't know why, but imagefilter() throw some fatal
|
|
// error if there is all 4 possible arguments, so i must give him
|
|
// only that, which are required at call time (depends of filter)
|
|
$argc = func_num_args();
|
|
$argv = func_get_args();
|
|
switch ($argc) {
|
|
case 1: return imagefilter($this->body, self::$filters[$filter]);
|
|
case 2: return imagefilter($this->body, self::$filters[$filter], $argv[1]);
|
|
case 3: return imagefilter($this->body, self::$filters[$filter], $argv[1], $argv[2]);
|
|
default:
|
|
return imagefilter($this->body, self::$filters[$filter], $argv[1], $argv[2], $argv[3]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply negate filter to an image
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function filterNegate()
|
|
{
|
|
return $this->filter('negate');
|
|
}
|
|
|
|
/**
|
|
* Apply grayscale filter to an image
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function filterGrayscale()
|
|
{
|
|
return $this->filter('grayscale');
|
|
}
|
|
|
|
/**
|
|
* Apply brightness filter to an image
|
|
*
|
|
* @param integer $brightness
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function filterBrightness($value)
|
|
{
|
|
return $this->filter('brightness', $value);
|
|
}
|
|
|
|
/**
|
|
* Apply contrast filter to an image
|
|
*
|
|
* @param integer $contrast
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function filterContrast($value)
|
|
{
|
|
return $this->filter('contrast', $value);
|
|
}
|
|
|
|
/**
|
|
* Apply colorize filter to an image
|
|
*
|
|
* Color can be as html notation, like 'ffffff' (white) or '000000'
|
|
* (black), either too three arguments, each one power of red ($r),
|
|
* green ($g) or blue ($b).
|
|
*
|
|
* @param mixed $r
|
|
* @param integer $g
|
|
* @param integer $b
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function filterColorize($r, $g=null, $b=null)
|
|
{
|
|
if (is_null($g) || is_null($b)) {
|
|
$rgb = $this->hex2rgb($r);
|
|
if (false === $rgb) {
|
|
return false;
|
|
}
|
|
list($r, $g, $b) = $rgb;
|
|
}
|
|
return $this->filter('colorize', $r, $g, $b);
|
|
}
|
|
|
|
/**
|
|
* Apply edge filter to an image
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function filterEdge()
|
|
{
|
|
return $this->filter('edge');
|
|
}
|
|
|
|
/**
|
|
* Apply emboss filter to an image
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function filterEmboss()
|
|
{
|
|
return $this->filter('emboss');
|
|
}
|
|
/**
|
|
* Apply gaussian blur filter to an image
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function filterGaussian()
|
|
{
|
|
return $this->filter('gaussian');
|
|
}
|
|
/**
|
|
* Apply blur filter to an image
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function filterBlur()
|
|
{
|
|
return $this->filter('blur');
|
|
}
|
|
/**
|
|
* Apply sketchy filter to an image
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function filterSketchy()
|
|
{
|
|
return $this->filter('sketchy');
|
|
}
|
|
/**
|
|
* Apply smooth filter to an image
|
|
*
|
|
* @param integer $value smoothness
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function filterSmooth($value)
|
|
{
|
|
return $this->filter('smooth', $value);
|
|
}
|
|
/**
|
|
* Rotate image for given angle
|
|
*
|
|
* @param integer $angle
|
|
* @param string $bgcolor background color
|
|
* @param boolean $ignore_transparent {@link http://php.net/imagerotate}
|
|
*
|
|
* @access public
|
|
*/
|
|
public function rotate($angle, $bgColor='ffffff', $ignoreTransparent=false)
|
|
{
|
|
$this->isInitialized();
|
|
|
|
if (!function_exists('imagerotate')) {
|
|
throw new BadMethodCallException('Incorrect function imagerotate.', 120);
|
|
}
|
|
|
|
$angle = (float)$angle;
|
|
$color = $this->color($bgColor);
|
|
$this->body = imagerotate($this->body, $angle, $color, $ignoreTransparent);
|
|
}
|
|
|
|
/**
|
|
* Flip image in vertical.
|
|
*
|
|
* @return bool
|
|
* @access public
|
|
*/
|
|
public function flipVertical()
|
|
{
|
|
return $this->flip('vertical');
|
|
}
|
|
|
|
/**
|
|
* Flip image in horizontal.
|
|
*
|
|
* @return bool
|
|
* @access public
|
|
*/
|
|
public function flipHorizontal()
|
|
{
|
|
return $this->flip('horizontal');
|
|
}
|
|
|
|
/**
|
|
* Flip image in both directions.
|
|
*
|
|
* @return bool
|
|
* @access public
|
|
*/
|
|
public function flipBoth()
|
|
{
|
|
return $this->flip('both');
|
|
}
|
|
|
|
/**
|
|
* Flips image in $direction direction.
|
|
*
|
|
* $direction can be:
|
|
* - vertical
|
|
* - horizontal
|
|
* - both
|
|
*
|
|
* @param string $direction
|
|
* @return bool
|
|
*/
|
|
public function flip($direction='both')
|
|
{
|
|
$dst = $this->newImage();
|
|
|
|
switch( $direction ) {
|
|
case 'horizontal':
|
|
for($i=0; $i<$this->properties['height']; ++$i) {
|
|
imagecopy($dst, $this->body, 0, $this->properties['height']-$i-1, 0, $i, $this->properties['width'], 1);
|
|
}
|
|
break;
|
|
|
|
case 'vertical':
|
|
for($i=0; $i<$this->properties['width']; ++$i) {
|
|
imagecopy($dst, $this->body, $this->properties['width']-$i-1, 0, $i, 0, 1, $this->properties['height']);
|
|
}
|
|
break;
|
|
|
|
case 'both':
|
|
for($i=0; $i<$this->properties['width']; ++$i) {
|
|
imagecopy($dst, $this->body, $this->properties['width']-$i-1, 0, $i, 0, 1, $this->properties['height']);
|
|
}
|
|
|
|
$rowBuffer = $this->newImage(null, 1, true);
|
|
for($i=0 ; $i<($this->properties['height']/2) ; ++$i ) {
|
|
imagecopy($rowBuffer, $dst , 0, 0, 0, $this->properties['height']-$i-1, $this->properties['width'], 1);
|
|
imagecopy($dst, $dst, 0, $this->properties['height']-$i-1, 0, $i, $this->properties['width'], 1);
|
|
imagecopy($dst, $rowBuffer, 0, $i, 0, 0, $this->properties['width'], 1);
|
|
}
|
|
|
|
imagedestroy($rowBuffer);
|
|
break;
|
|
}
|
|
|
|
return $this->swap($dst);
|
|
}
|
|
|
|
/**
|
|
* Apply an border to image
|
|
*
|
|
* Border can be solid, or pattern ({@link http://php.net/imagesetstyle}).
|
|
* Each of borders can be other color/style, and thickness.
|
|
*
|
|
* If $thickness is an integer, it means is one value (thickness) for
|
|
* all borders. $thickness can be an array too, and it must have 4
|
|
* elements, one per one border.
|
|
*
|
|
* Colors/style can be set one for all edges (only $colT set), different
|
|
* for top/bottom and left/right edges (set $colT and $colR,
|
|
* and different for any of edge (all 4 $col* must be set).
|
|
*
|
|
* Colors can be as hex value in html notation, or result of
|
|
* imagecolorallocate() ({@link http://php.net/imagecolorallocate}).
|
|
*
|
|
* If You want to set pattern for edges, You must set proper arrays
|
|
* ({@link http://php.net/imagesetstyle}).
|
|
*
|
|
* @param mixed $thickness
|
|
* @param mixed $colT
|
|
* @param mixed $colR
|
|
* @param mixed $colB
|
|
* @param mixed $colL
|
|
*
|
|
* @return boolean
|
|
* @throws InvalidArgumentException
|
|
* @access public
|
|
*/
|
|
public function border($thickness, $colT, $colR=null, $colB=null, $colL=null)
|
|
{
|
|
if (!is_array($thickness)) { //all borders
|
|
$thickness = array_fill(0, 4, $thickness);
|
|
} elseif (2 == count($thickness)) { //top and bottom, left and right
|
|
$tmp = $thickness;
|
|
$thickness = array($tmp[0], $tmp[1], $tmp[0], $tmp[1]);
|
|
unset($tmp);
|
|
} elseif (4 != count($thickness)) { //four different
|
|
throw new InvalidArgumentException('Incorrect quant of elements in "$thickness" parameter.', 144);
|
|
}
|
|
|
|
if (is_null($colR)) { //all borders have one style
|
|
$colR = $colB = $colL = $colT;
|
|
} elseif (is_null($colB)) { //borders top and bottom have one style, left and right - second
|
|
$colB = $colT;
|
|
$colL = $colR;
|
|
}
|
|
$col = array($colT, $colR, $colB, $colL);
|
|
|
|
$this->drawLine( //top
|
|
array(0, $thickness[0]/2),
|
|
array($this->properties['width'], $thickness[0]/2),
|
|
$colT, $thickness[0]);
|
|
$this->drawLine( //right
|
|
array($this->properties['width'] - ($thickness[1]/2), 0),
|
|
array($this->properties['width'] - ($thickness[1]/2),
|
|
$this->properties['height']),
|
|
$colR, $thickness[1]);
|
|
$this->drawLine( //left
|
|
array($this->properties['width'],
|
|
$this->properties['height'] - ($thickness[2]/2) ),
|
|
array(0, $this->properties['height'] - ($thickness[2]/2) ),
|
|
$colB, $thickness[2]);
|
|
$this->drawLine( //bottom
|
|
array($thickness[3]/2, $this->properties['height']),
|
|
array($thickness[3]/2, 0),
|
|
$colL, $thickness[3]);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Add layer on top of image.
|
|
*
|
|
* If $layer is string, it suppose to be file name, which be loaded and
|
|
* merged with current image.
|
|
* $layer can also be an image object ({@link mImage::get()}).
|
|
*
|
|
* Can be used to adding some blenda do thumbnails, for example.
|
|
*
|
|
* @param mixed $layer
|
|
* @param integer $alpha opacity of new layer
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function addLayer($layer, $alpha=100)
|
|
{
|
|
if (is_string($layer) && is_file($layer)) {
|
|
$i = new mImage($layer);
|
|
$layer = $i->get();
|
|
}
|
|
if (!$layer) {
|
|
return false;
|
|
}
|
|
|
|
return imagecopymerge($this->body, $layer, 0, 0, 0, 0, imagesx($layer),
|
|
imagesy($layer), $alpha);
|
|
}
|
|
|
|
/**
|
|
* Allocate color.
|
|
*
|
|
* Can be used when adding borders etc.
|
|
* {@link http://php.net/imagecolorallocate}
|
|
*
|
|
* @param string $hex color in html notation
|
|
* @param resource $img if not null, used by imagecolorallocate
|
|
*
|
|
* @return integer
|
|
* @access public
|
|
*/
|
|
public function color($hex, &$img=null)
|
|
{
|
|
list($r, $g, $b) = $this->hex2rgb($hex);
|
|
if (is_null($img)) {
|
|
return imagecolorallocate($this->body, $r, $g, $b);
|
|
} else {
|
|
return imagecolorallocate($img, $r, $g, $b);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return current image object
|
|
*
|
|
* @return resource
|
|
* @access public
|
|
*/
|
|
public function get()
|
|
{
|
|
return clone $this->body;
|
|
}
|
|
|
|
/**
|
|
* Draw a line on image
|
|
*
|
|
* Currently used only for drawig borders/
|
|
* $begin and $end holds coordinates of begin and end points of line.
|
|
* $color can be:
|
|
* - integer - if line have to be solid
|
|
* - array - if line have to be an pattern ({@link http://php.net/imagesestyle})
|
|
*
|
|
* @param array $begin
|
|
* @param array $end
|
|
* @param mixed $color
|
|
* @param integer $thickness
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
protected function drawLine(array $begin, array $end, $color, $thickness)
|
|
{
|
|
imagesetthickness($this->body, $thickness);
|
|
|
|
if (!is_array($color)) { //solid
|
|
if (is_string($color)) {
|
|
$color = $this->color($color);
|
|
}
|
|
imageline($this->body,
|
|
$begin[0], $begin[1],
|
|
$end[0], $end[1],
|
|
$color);
|
|
} else { //pattern
|
|
imagesetstyle($this->body, $color);
|
|
imageline($this->body,
|
|
$begin[0], $begin[1],
|
|
$end[0], $end[1],
|
|
IMG_COLOR_STYLED);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks for image that was loaded
|
|
*
|
|
* If image wasn't loaded, and $exc == true, an exception is raised.
|
|
* If $exc == false, isInitialized() return false.
|
|
*
|
|
* @param boolean $silent if true, an exception is raised when image wasn't loaded
|
|
*
|
|
* @return boolean
|
|
* @throws LogicException
|
|
* @access protected
|
|
*/
|
|
protected function isInitialized($silent=false)
|
|
{
|
|
if (is_null($this->body)) {
|
|
if ($silent) {
|
|
return false;
|
|
} else {
|
|
throw new LogicException('Image no loaded - call mImage::open() first.', 100);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Converts html notation of color to rgb array
|
|
*
|
|
* Return false or 3 element array with rgb data.
|
|
*
|
|
* @param string $hex color in html notation
|
|
*
|
|
* @return mixed
|
|
* @access protected
|
|
*/
|
|
protected function hex2rgb($hex)
|
|
{
|
|
if (6 != strlen($hex)) {
|
|
return false;
|
|
}
|
|
|
|
$data = str_split($hex, 2);
|
|
$data[0] = hexdec($data[0]);
|
|
$data[1] = hexdec($data[1]);
|
|
$data[2] = hexdec($data[2]);
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Creates new image object
|
|
*
|
|
* If $width or $height are null, these values are got from
|
|
* $this->{width,height}.
|
|
*
|
|
* @param integer $width
|
|
* @param integer $height
|
|
* @param boolean $truecolor
|
|
*
|
|
* @return object
|
|
* @access protected
|
|
*/
|
|
protected function newImage($width=null, $height=null, $truecolor=null)
|
|
{
|
|
//docelowa szerokosc
|
|
if (is_null($width)) {
|
|
$width = $this->properties['width'];
|
|
}
|
|
//docelowa wysokosc
|
|
if (is_null($height)) {
|
|
$height = $this->properties['height'];
|
|
}
|
|
if (is_null($truecolor)) {
|
|
$truecolor = $this->properties['truecolor'];
|
|
}
|
|
|
|
if ($truecolor) {
|
|
return imagecreatetruecolor($width, $height);
|
|
} else {
|
|
return imagecreate($width, $height);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check for correct image format
|
|
*
|
|
* If format is incorrect, it return default format used by mImage class.
|
|
* See: {@link mImage::FORMAT}, {@link mImage::$formats}
|
|
*
|
|
* @param string $format
|
|
*
|
|
* @return string
|
|
* @access protected
|
|
*/
|
|
protected function checkFormat($format=null)
|
|
{
|
|
if (is_null($format) || !array_key_exists($format, self::$formats)) {
|
|
$format = self::FORMAT;
|
|
}
|
|
if ('jpg' == $format) {
|
|
$format = 'jpeg';
|
|
}
|
|
return $format;
|
|
}
|
|
|
|
/**
|
|
* Calculate proportional size of scaled image
|
|
*
|
|
* @param integer $max_width
|
|
* @param integer $max_height
|
|
*
|
|
* @return array
|
|
* @access protected
|
|
*/
|
|
protected function calculateSize(&$max_width, &$max_height)
|
|
{
|
|
$div_width = (double)($this->properties['width'] / $max_width);
|
|
$div_height = (double)($this->properties['height'] / $max_height);
|
|
|
|
$div = max($div_width, $div_height);
|
|
|
|
$ret = array();
|
|
$ret[] = (double)($this->properties['width'] / $div);
|
|
$ret[] = (double)($this->properties['height'] / $div);
|
|
$ret[] = &$div;
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Calculate position of scaled image
|
|
*
|
|
* (coordinates of left top point and
|
|
*
|
|
* @param integer $max_width
|
|
* @param integer $max_height
|
|
* @param string $position
|
|
*
|
|
* @return array
|
|
* @access protected
|
|
*/
|
|
protected function calculatePosition(&$max_width, &$max_height, $position)
|
|
{
|
|
//'c', 'lt', 'ct', 'rt', 'lc', 'rc', 'lb', 'cb', 'rb'
|
|
list($th_w, $th_h) = $this->calculateSize($max_width, $max_height);
|
|
$ret = array(0, 0, 0, 0);
|
|
switch ($position)
|
|
{
|
|
case 'lt':
|
|
break;
|
|
case 'ct':
|
|
$ret[0] = ($max_width-$th_w)/2;
|
|
break;
|
|
case 'rt':
|
|
$ret[0] = $max_width-$th_w;
|
|
break;
|
|
case 'lc':
|
|
$ret[1] = ($max_height-$th_h)/2;
|
|
break;
|
|
case 'rc':
|
|
$ret[0] = $max_width-$th_w;
|
|
$ret[1] = ($max_height-$th_h)/2;
|
|
break;
|
|
case 'lb':
|
|
$ret[1] = $max_height-$th_h;
|
|
break;
|
|
case 'cb':
|
|
$ret[0] = ($max_width-$th_w)/2;
|
|
$ret[1] = $max_height-$th_h;
|
|
break;
|
|
case 'rb':
|
|
$ret[0] = $max_width-$th_w;
|
|
$ret[1] = $max_height-$th_h;
|
|
break;
|
|
default:
|
|
if ($th_w > $th_h) {
|
|
$ret[1] = ($max_height - $th_h) / 2;
|
|
} else {
|
|
$ret[0] = ($max_width - $th_w) / 2;
|
|
}
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Put given argument as content of mImage::$body
|
|
*
|
|
* @param object $dst
|
|
*
|
|
* @return boolean
|
|
* @access protected
|
|
*/
|
|
protected function swap(&$dst)
|
|
{
|
|
if (is_resource($dst)) {
|
|
if (!is_null($this->body)) {
|
|
imagedestroy($this->body);
|
|
}
|
|
$this->body = $dst;
|
|
|
|
$this->properties['width'] = imagesx($this->body);
|
|
$this->properties['height'] = imagesy($this->body);
|
|
$this->properties['truecolor'] = imageistruecolor($this->body);
|
|
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Overloaded setter
|
|
*
|
|
* Use mImage::$properties as store container.
|
|
*
|
|
* @param string $k name of property
|
|
* @param mixed $v value of property
|
|
*
|
|
* @throws InvalidArgumentException
|
|
* @access public
|
|
*/
|
|
public function __set($k, $v)
|
|
{
|
|
if (array_key_exists($k, $this->properties)) {
|
|
throw new InvalidArgumentException(sprintf('Attribute "%s" is read only.', $k), 142);
|
|
} else {
|
|
throw new InvalidArgumentException(sprintf('Attribute "%s" doesn\'t exists.', $k), 145);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Overloaded getter
|
|
*
|
|
* Use mImage::$properties as store container.
|
|
*
|
|
* @param string $k name of property
|
|
*
|
|
* @return mixed
|
|
* @throws InvalidArgumentException
|
|
* @access public
|
|
*/
|
|
public function __get($k)
|
|
{
|
|
if (array_key_exists($k, $this->properties)) {
|
|
return $this->properties[$k];
|
|
} else {
|
|
throw new InvalidArgumentException(sprintf('Attribute "%s" doesn\'t exists.', $k), 145);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Overloaded isset()
|
|
*
|
|
* Use mImage::$properties as store container.
|
|
*
|
|
* @param string $k name of property
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
public function __isset($k)
|
|
{
|
|
if (array_key_exists($k, $this->properties) &&
|
|
!is_null($this->properties[$k])) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return array of available filters
|
|
*
|
|
* Can be usefull when we want to show which filters are available
|
|
* at runtime.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getFilters()
|
|
{
|
|
return self::$filters;
|
|
}
|
|
}
|
|
|
|
?>
|