* @author Peter Bowyer * @author Philippe Jausions * @copyright 2002-2005 The PHP Group * @license http://www.php.net/license/3_0.txt PHP License 3.0 * @version CVS: $Id$ * @link http://pear.php.net/package/Image_Transform */ require_once 'Image/Transform.php'; /** * GD implementation for Image_Transform package * * Usage : * $img =& Image_Transform::factory('GD'); * $angle = -78; * $img->load('magick.png'); * * if ($img->rotate($angle, array( * 'autoresize' => true, * 'color_mask' => array(255, 0, 0)))) { * $img->addText(array( * 'text' => 'Rotation ' . $angle, * 'x' => 0, * 'y' => 100, * 'font' => '/usr/share/fonts/default/TrueType/cogb____.ttf')); * $img->display(); * } else { * echo "Error"; * } * $img->free(); * * @category Image * @package Image_Transform * @subpackage Image_Transform_Driver_GD * @author Alan Knowles * @author Peter Bowyer * @author Philippe Jausions * @copyright 2002-2005 The PHP Group * @license http://www.php.net/license/3_0.txt PHP License 3.0 * @version Release: @package_version@ * @link http://pear.php.net/package/Image_Transform * @since PHP 4.0 */ class Image_Transform_Driver_GD extends Image_Transform { /** * Holds the image resource for manipulation * * @var resource $imageHandle * @access protected */ var $imageHandle = null; /** * Holds the original image file * * @var resource $imageHandle * @access protected */ var $old_image = null; /** * Check settings */ function Image_Transform_Driver_GD() { $this->__construct(); } // End function Image /** * Check settings * * @since PHP 5 */ function __construct() { if (!PEAR::loadExtension('gd')) { $this->isError(PEAR::raiseError("GD library is not available.", IMAGE_TRANSFORM_ERROR_UNSUPPORTED)); } else { $types = ImageTypes(); if ($types & IMG_PNG) { $this->_supported_image_types['png'] = 'rw'; } if (($types & IMG_GIF) || function_exists('imagegif')) { $this->_supported_image_types['gif'] = 'rw'; } elseif (function_exists('imagecreatefromgif')) { $this->_supported_image_types['gif'] = 'r'; } if ($types & IMG_JPG) { $this->_supported_image_types['jpeg'] = 'rw'; } if ($types & IMG_WBMP) { $this->_supported_image_types['wbmp'] = 'rw'; } if (!$this->_supported_image_types) { $this->isError(PEAR::raiseError("No supported image types available", IMAGE_TRANSFORM_ERROR_UNSUPPORTED)); } } } // End function Image /** * Loads an image from file * * @param string $image filename * @return bool|PEAR_Error TRUE or a PEAR_Error object on error * @access public */ function load($image) { $this->free(); $this->image = $image; $result = $this->_get_image_details($image); if (PEAR::isError($result)) { return $result; } if (!$this->supportsType($this->type, 'r')) { return PEAR::raiseError('Image type not supported for input', IMAGE_TRANSFORM_ERROR_UNSUPPORTED); } $functionName = 'ImageCreateFrom' . $this->type; $this->imageHandle = $functionName($this->image); if (!$this->imageHandle) { $this->imageHandle = null; return PEAR::raiseError('Error while loading image file.', IMAGE_TRANSFORM_ERROR_IO); } return true; } // End load /** * Adds a border of constant width around an image * * @param int $border_width Width of border to add * @author Peter Bowyer * @return bool TRUE * @access public */ function addBorder($border_width, $color = '') { $this->new_x = $this->img_x + 2 * $border_width; $this->new_y = $this->img_y + 2 * $border_width; $new_img = $this->_createImage($new_x, $new_y, $this->true_color); $options = array('pencilColor', $color); $color = $this->_getColor('pencilColor', $options, array(0, 0, 0)); if ($color) { if ($this->true_color) { $c = imagecolorresolve($this->imageHandle, $color[0], $color[1], $color[2]); imagefill($new_img, 0, 0, $c); } else { imagecolorset($new_img, imagecolorat($new_img, 0, 0), $color[0], $color[1], $color[2]); } } ImageCopy($new_img, $this->imageHandle, $border_width, $border_width, 0, 0, $this->img_x, $this->img_y); $this->imageHandle = $new_img; $this->resized = true; return true; } /** * addText * * @param array $params Array contains options * array( * 'text' The string to draw * 'x' Horizontal position * 'y' Vertical Position * 'color' Font color * 'font' Font to be used * 'size' Size of the fonts in pixel * 'resize_first' Tell if the image has to be resized * before drawing the text * ) * * @return bool|PEAR_Error TRUE or a PEAR_Error object on error */ function addText($params) { $params = array_merge($this->_get_default_text_params(), $params); extract($params); $options = array('fontColor' => $color); $color = $this->_getColor('fontColor', $options, array(0, 0, 0)); $c = imagecolorresolve ($this->imageHandle, $color[0], $color[1], $color[2]); if ('ttf' == substr($font, -3)) { ImageTTFText($this->imageHandle, $size, $angle, $x, $y, $c, $font, $text); } else { ImagePSText($this->imageHandle, $size, $angle, $x, $y, $c, $font, $text); } return true; } // End addText /** * Rotates image by the given angle * * Uses a fast rotation algorythm for custom angles * or lines copy for multiple of 90 degrees * * @param int $angle Rotation angle * @param array $options array( * 'canvasColor' => array(r ,g, b), named color or #rrggbb * ) * @author Pierre-Alain Joye * @return bool|PEAR_Error TRUE or a PEAR_Error object on error * @access public */ function rotate($angle, $options = null) { if (($angle % 360) == 0) { return true; } $color_mask = $this->_getColor('canvasColor', $options, array(255, 255, 255)); $mask = imagecolorresolve($this->imageHandle, $color_mask[0], $color_mask[1], $color_mask[2]); $this->old_image = $this->imageHandle; // Multiply by -1 to change the sign, so the image is rotated clockwise $this->imageHandle = ImageRotate($this->imageHandle, $angle * -1, $mask); return true; } /** * Horizontal mirroring * * @return mixed TRUE or PEAR_Error object on error * @access public * @see flip() **/ function mirror() { $new_img = $this->_createImage(); for ($x = 0; $x < $this->new_x; ++$x) { imagecopy($new_img, $this->imageHandle, $x, 0, $this->new_x - $x - 1, 0, 1, $this->new_y); } imagedestroy($this->imageHandle); $this->imageHandle = $new_img; return true; } /** * Vertical mirroring * * @return TRUE or PEAR Error object on error * @access public * @see mirror() **/ function flip() { $new_img = $this->_createImage(); for ($y = 0; $y < $this->new_y; ++$y) { imagecopy($new_img, $this->imageHandle, 0, $y, 0, $this->new_y - $y - 1, $this->new_x, 1); } imagedestroy($this->imageHandle); $this->imageHandle = $new_img; /* for very large images we may want to use the following Needs to find out what is the threshhold for ($x = 0; $x < $this->new_x; ++$x) { for ($y1 = 0; $y1 < $this->new_y / 2; ++$y1) { $y2 = $this->new_y - 1 - $y1; $color1 = imagecolorat($this->imageHandle, $x, $y1); $color2 = imagecolorat($this->imageHandle, $x, $y2); imagesetpixel($this->imageHandle, $x, $y1, $color2); imagesetpixel($this->imageHandle, $x, $y2, $color1); } } */ return true; } /** * Crops image by size and start coordinates * * @param int width Cropped image width * @param int height Cropped image height * @param int x X-coordinate to crop at * @param int y Y-coordinate to crop at * @return bool|PEAR_Error TRUE or a PEAR_Error object on error * @access public */ function crop($width, $height, $x = 0, $y = 0) { // Sanity check if (!$this->intersects($width, $height, $x, $y)) { return PEAR::raiseError('Nothing to crop', IMAGE_TRANSFORM_ERROR_OUTOFBOUND); } $x = min($this->new_x, max(0, $x)); $y = min($this->new_y, max(0, $y)); $width = min($width, $this->new_x - $x); $height = min($height, $this->new_y - $y); $new_img = $this->_createImage($width, $height); if (!imagecopy($new_img, $this->imageHandle, 0, 0, $x, $y, $width, $height)) { imagedestroy($new_img); return PEAR::raiseError('Failed transformation: crop()', IMAGE_TRANSFORM_ERROR_FAILED); } $this->old_image = $this->imageHandle; $this->imageHandle = $new_img; $this->resized = true; $this->new_x = $width; $this->new_y = $height; return true; } /** * Converts the image to greyscale * * @return bool|PEAR_Error TRUE or a PEAR_Error object on error * @access public */ function greyscale() { imagecopymergegray($this->imageHandle, $this->imageHandle, 0, 0, 0, 0, $this->new_x, $this->new_y, 0); return true; } /** * Resize Action * * For GD 2.01+ the new copyresampled function is used * It uses a bicubic interpolation algorithm to get far * better result. * * Options: * - scaleMethod: "pixel" or "smooth" * * @param int $new_x New width * @param int $new_y New height * @param mixed $options Optional parameters * * @return bool|PEAR_Error TRUE on success or PEAR_Error object on error * @access protected */ function _resize($new_x, $new_y, $options = null) { if ($this->resized === true) { return PEAR::raiseError('You have already resized the image without saving it. Your previous resizing will be overwritten', null, PEAR_ERROR_TRIGGER, E_USER_NOTICE); } if ($this->new_x == $new_x && $this->new_y == $new_y) { return true; } $scaleMethod = $this->_getOption('scaleMethod', $options, 'smooth'); // Make sure to get a true color image if doing resampled resizing // otherwise get the same type of image $trueColor = ($scaleMethod == 'pixel') ? null : true; $new_img = $this->_createImage($new_x, $new_y, $trueColor); $icr_res = null; if ($scaleMethod != 'pixel' && function_exists('ImageCopyResampled')) { $icr_res = ImageCopyResampled($new_img, $this->imageHandle, 0, 0, 0, 0, $new_x, $new_y, $this->img_x, $this->img_y); } if (!$icr_res) { ImageCopyResized($new_img, $this->imageHandle, 0, 0, 0, 0, $new_x, $new_y, $this->img_x, $this->img_y); } $this->old_image = $this->imageHandle; $this->imageHandle = $new_img; $this->resized = true; $this->new_x = $new_x; $this->new_y = $new_y; return true; } /** * Adjusts the image gamma * * @param float $outputgamma * * @return bool|PEAR_Error TRUE or a PEAR_Error object on error * @access public */ function gamma($outputgamma = 1.0) { if ($outputgamma != 1.0) { ImageGammaCorrect($this->imageHandle, 1.0, $outputgamma); } return true; } /** * Helper method to save to a file or output the image * * @param string $filename the name of the file to write to (blank to output) * @param string $types define the output format, default * is the current used format * @param int $quality output DPI, default is 75 * * @return bool|PEAR_Error TRUE on success or PEAR_Error object on error * @access protected */ function _generate($filename, $type = '', $quality = null) { $type = strtolower(($type == '') ? $this->type : $type); $options = (is_array($quality)) ? $quality : array(); switch ($type) { case 'jpg': $type = 'jpeg'; case 'jpeg': if (is_numeric($quality)) { $options['quality'] = $quality; } $quality = $this->_getOption('quality', $options, 75); break; } if (!$this->supportsType($type, 'w')) { return PEAR::raiseError('Image type not supported for output', IMAGE_TRANSFORM_ERROR_UNSUPPORTED); } if ($filename == '') { header('Content-type: ' . $this->getMimeType($type)); $action = 'output image'; } else { $action = 'save image to file'; } $functionName = 'image' . $type; switch ($type) { case 'jpeg': $result = $functionName($this->imageHandle, $filename, $quality); break; default: if ($filename == '') { $result = $functionName($this->imageHandle); } else { $result = $functionName($this->imageHandle, $filename); } } if (!$result) { return PEAR::raiseError('Couldn\'t ' . $action, IMAGE_TRANSFORM_ERROR_IO); } $this->imageHandle = $this->old_image; if (!$this->keep_settings_on_save) { $this->free(); } return true; } // End save /** * Displays image without saving and lose changes. * * This method adds the Content-type HTTP header * * @param string $type (JPEG, PNG...); * @param int $quality 75 * * @return bool|PEAR_Error TRUE or PEAR_Error object on error * @access public */ function display($type = '', $quality = null) { return $this->_generate('', $type, $quality);; } /** * Saves the image to a file * * @param string $filename the name of the file to write to * @param string $type the output format, default * is the current used format * @param int $quality default is 75 * * @return bool|PEAR_Error TRUE on success or PEAR_Error object on error * @access public */ function save($filename, $type = '', $quality = null) { if (!trim($filename)) { return PEAR::raiseError('Filename missing', IMAGE_TRANSFORM_ERROR_ARGUMENT); } return $this->_generate($filename, $type, $quality); } /** * Destroys image handle * * @access public */ function free() { $this->resized = false; if (is_resource($this->imageHandle)) { ImageDestroy($this->imageHandle); } $this->imageHandle = null; if (is_resource($this->old_image)){ ImageDestroy($this->old_image); } $this->old_image = null; } /** * Returns a new image for temporary processing * * @param int $width width of the new image * @param int $height height of the new image * @param bool $trueColor force which type of image to create * @return resource a GD image resource * @access protected */ function _createImage($width = -1, $height = -1, $trueColor = null) { if ($width == -1) { $width = $this->new_x; } if ($height == -1) { $height = $this->new_y; } $new_img = null; if (is_null($trueColor)) { if (function_exists('imageistruecolor')) { $createtruecolor = imageistruecolor($this->imageHandle); } else { $createtruecolor = true; } } else { $createtruecolor = $trueColor; } if ($createtruecolor && function_exists('ImageCreateTrueColor')) { $new_img = @ImageCreateTrueColor($width, $height); } if (!$new_img) { $new_img = ImageCreate($width, $height); imagepalettecopy($new_img, $this->imageHandle); $color = imagecolortransparent($this->imageHandle); if ($color != -1) { imagecolortransparent($new_img, $color); imagefill($new_img, 0, 0, $color); } } return $new_img; } }