3 /* vim: set expandtab tabstop=4 shiftwidth=4: */
6 * GD implementation for Image_Transform package
10 * LICENSE: This source file is subject to version 3.0 of the PHP license
11 * that is available through the world-wide-web at the following URI:
12 * http://www.php.net/license/3_0.txt. If you did not receive a copy of
13 * the PHP License and are unable to obtain it through the web, please
14 * send a note to license@php.net so we can mail you a copy immediately.
17 * @package Image_Transform
18 * @subpackage Image_Transform_Driver_GD
19 * @author Alan Knowles <alan@akbkhome.com>
20 * @author Peter Bowyer <peter@mapledesign.co.uk>
21 * @author Philippe Jausions <Philippe.Jausions@11abacus.com>
22 * @copyright 2002-2005 The PHP Group
23 * @license http://www.php.net/license/3_0.txt PHP License 3.0
24 * @version CVS: $Id: GD.php 287351 2009-08-16 03:28:48Z clockwerx $
25 * @link http://pear.php.net/package/Image_Transform
28 require_once 'Image/Transform.php';
31 * GD implementation for Image_Transform package
34 * $img =& Image_Transform::factory('GD');
36 * $img->load('magick.png');
38 * if ($img->rotate($angle, array(
39 * 'autoresize' => true,
40 * 'color_mask' => array(255, 0, 0)))) {
41 * $img->addText(array(
42 * 'text' => 'Rotation ' . $angle,
45 * 'font' => '/usr/share/fonts/default/TrueType/cogb____.ttf'));
53 * @package Image_Transform
54 * @subpackage Image_Transform_Driver_GD
55 * @author Alan Knowles <alan@akbkhome.com>
56 * @author Peter Bowyer <peter@mapledesign.co.uk>
57 * @author Philippe Jausions <Philippe.Jausions@11abacus.com>
58 * @copyright 2002-2005 The PHP Group
59 * @license http://www.php.net/license/3_0.txt PHP License 3.0
60 * @version Release: @package_version@
61 * @link http://pear.php.net/package/Image_Transform
64 class Image_Transform_Driver_GD extends Image_Transform
67 * Holds the image resource for manipulation
69 * @var resource $imageHandle
72 var $imageHandle = null;
75 * Holds the original image file
77 * @var resource $imageHandle
85 function Image_Transform_Driver_GD()
88 } // End function Image
95 function __construct()
97 if (!PEAR::loadExtension('gd')) {
98 $this->isError(PEAR::raiseError("GD library is not available.",
99 IMAGE_TRANSFORM_ERROR_UNSUPPORTED));
101 $types = ImageTypes();
102 if ($types & IMG_PNG) {
103 $this->_supported_image_types['png'] = 'rw';
105 if (($types & IMG_GIF)
106 || function_exists('imagegif')) {
107 $this->_supported_image_types['gif'] = 'rw';
108 } elseif (function_exists('imagecreatefromgif')) {
109 $this->_supported_image_types['gif'] = 'r';
111 if ($types & IMG_JPG) {
112 $this->_supported_image_types['jpeg'] = 'rw';
114 if ($types & IMG_WBMP) {
115 $this->_supported_image_types['wbmp'] = 'rw';
117 if (!$this->_supported_image_types) {
118 $this->isError(PEAR::raiseError("No supported image types available", IMAGE_TRANSFORM_ERROR_UNSUPPORTED));
122 } // End function Image
125 * Loads an image from file
127 * @param string $image filename
128 * @return bool|PEAR_Error TRUE or a PEAR_Error object on error
131 function load($image)
135 $this->image = $image;
136 $result = $this->_get_image_details($image);
137 if (PEAR::isError($result)) {
140 if (!$this->supportsType($this->type, 'r')) {
141 return PEAR::raiseError('Image type not supported for input',
142 IMAGE_TRANSFORM_ERROR_UNSUPPORTED);
145 $functionName = 'ImageCreateFrom' . $this->type;
146 $this->imageHandle = $functionName($this->image);
147 if (!$this->imageHandle) {
148 $this->imageHandle = null;
149 return PEAR::raiseError('Error while loading image file.',
150 IMAGE_TRANSFORM_ERROR_IO);
157 * Returns the GD image handle
165 return $this->imageHandle;
166 }//function getHandle()
169 * Adds a border of constant width around an image
171 * @param int $border_width Width of border to add
172 * @author Peter Bowyer
176 function addBorder($border_width, $color = '')
178 $this->new_x = $this->img_x + 2 * $border_width;
179 $this->new_y = $this->img_y + 2 * $border_width;
181 $new_img = $this->_createImage($new_x, $new_y, $this->true_color);
183 $options = array('pencilColor', $color);
184 $color = $this->_getColor('pencilColor', $options, array(0, 0, 0));
186 if ($this->true_color) {
187 $c = imagecolorresolve($this->imageHandle, $color[0], $color[1], $color[2]);
188 imagefill($new_img, 0, 0, $c);
190 imagecolorset($new_img, imagecolorat($new_img, 0, 0), $color[0], $color[1], $color[2]);
193 ImageCopy($new_img, $this->imageHandle, $border_width, $border_width, 0, 0, $this->img_x, $this->img_y);
194 $this->imageHandle = $new_img;
195 $this->resized = true;
203 * @param array $params Array contains options
205 * 'text' The string to draw
206 * 'x' Horizontal position
207 * 'y' Vertical Position
209 * 'font' Font to be used
210 * 'size' Size of the fonts in pixel
211 * 'resize_first' Tell if the image has to be resized
212 * before drawing the text
215 * @return bool|PEAR_Error TRUE or a PEAR_Error object on error
217 function addText($params)
219 $this->oldImage = $this->imageHandle;
220 $params = array_merge($this->_get_default_text_params(), $params);
223 $options = array('fontColor' => $color);
224 $color = $this->_getColor('fontColor', $options, array(0, 0, 0));
226 $c = imagecolorresolve ($this->imageHandle, $color[0], $color[1], $color[2]);
228 if ('ttf' == substr($font, -3)) {
229 ImageTTFText($this->imageHandle, $size, $angle, $x, $y, $c, $font, $text);
231 ImagePSText($this->imageHandle, $size, $angle, $x, $y, $c, $font, $text);
238 * Rotates image by the given angle
240 * Uses a fast rotation algorythm for custom angles
241 * or lines copy for multiple of 90 degrees
243 * @param int $angle Rotation angle
244 * @param array $options array(
245 * 'canvasColor' => array(r ,g, b), named color or #rrggbb
247 * @author Pierre-Alain Joye
248 * @return bool|PEAR_Error TRUE or a PEAR_Error object on error
251 function rotate($angle, $options = null)
253 if (($angle % 360) == 0) {
257 $color_mask = $this->_getColor('canvasColor', $options,
258 array(255, 255, 255));
260 $mask = imagecolorresolve($this->imageHandle, $color_mask[0], $color_mask[1], $color_mask[2]);
262 $this->oldImage = $this->imageHandle;
264 // Multiply by -1 to change the sign, so the image is rotated clockwise
265 $this->imageHandle = ImageRotate($this->imageHandle, $angle * -1, $mask);
270 * Horizontal mirroring
272 * @return mixed TRUE or PEAR_Error object on error
278 $new_img = $this->_createImage();
279 for ($x = 0; $x < $this->new_x; ++$x) {
280 imagecopy($new_img, $this->imageHandle, $x, 0,
281 $this->new_x - $x - 1, 0, 1, $this->new_y);
283 imagedestroy($this->imageHandle);
284 $this->imageHandle = $new_img;
291 * @return TRUE or PEAR Error object on error
297 $new_img = $this->_createImage();
298 for ($y = 0; $y < $this->new_y; ++$y) {
299 imagecopy($new_img, $this->imageHandle, 0, $y,
300 0, $this->new_y - $y - 1, $this->new_x, 1);
302 imagedestroy($this->imageHandle);
303 $this->imageHandle = $new_img;
305 /* for very large images we may want to use the following
306 Needs to find out what is the threshhold
307 for ($x = 0; $x < $this->new_x; ++$x) {
308 for ($y1 = 0; $y1 < $this->new_y / 2; ++$y1) {
309 $y2 = $this->new_y - 1 - $y1;
310 $color1 = imagecolorat($this->imageHandle, $x, $y1);
311 $color2 = imagecolorat($this->imageHandle, $x, $y2);
312 imagesetpixel($this->imageHandle, $x, $y1, $color2);
313 imagesetpixel($this->imageHandle, $x, $y2, $color1);
320 * Crops image by size and start coordinates
322 * @param int width Cropped image width
323 * @param int height Cropped image height
324 * @param int x X-coordinate to crop at
325 * @param int y Y-coordinate to crop at
326 * @return bool|PEAR_Error TRUE or a PEAR_Error object on error
329 function crop($width, $height, $x = 0, $y = 0)
332 if (!$this->intersects($width, $height, $x, $y)) {
333 return PEAR::raiseError('Nothing to crop', IMAGE_TRANSFORM_ERROR_OUTOFBOUND);
335 $x = min($this->new_x, max(0, $x));
336 $y = min($this->new_y, max(0, $y));
337 $width = min($width, $this->new_x - $x);
338 $height = min($height, $this->new_y - $y);
339 $new_img = $this->_createImage($width, $height);
341 if (!imagecopy($new_img, $this->imageHandle, 0, 0, $x, $y, $width, $height)) {
342 imagedestroy($new_img);
343 return PEAR::raiseError('Failed transformation: crop()',
344 IMAGE_TRANSFORM_ERROR_FAILED);
347 $this->oldImage = $this->imageHandle;
348 $this->imageHandle = $new_img;
349 $this->resized = true;
351 $this->new_x = $width;
352 $this->new_y = $height;
357 * Converts the image to greyscale
359 * @return bool|PEAR_Error TRUE or a PEAR_Error object on error
362 function greyscale() {
363 imagecopymergegray($this->imageHandle, $this->imageHandle, 0, 0, 0, 0, $this->new_x, $this->new_y, 0);
370 * For GD 2.01+ the new copyresampled function is used
371 * It uses a bicubic interpolation algorithm to get far
374 * @param int $new_x New width
375 * @param int $new_y New height
376 * @param array $options Optional parameters
378 * <li>'scaleMethod': "pixel" or "smooth"</li>
381 * @return bool|PEAR_Error TRUE on success or PEAR_Error object on error
384 function _resize($new_x, $new_y, $options = null)
386 if ($this->resized === true) {
387 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);
390 if ($this->new_x == $new_x && $this->new_y == $new_y) {
394 $scaleMethod = $this->_getOption('scaleMethod', $options, 'smooth');
396 // Make sure to get a true color image if doing resampled resizing
397 // otherwise get the same type of image
398 $trueColor = ($scaleMethod == 'pixel') ? null : true;
399 $new_img = $this->_createImage($new_x, $new_y, $trueColor);
402 if ($scaleMethod != 'pixel' && function_exists('ImageCopyResampled')) {
403 $icr_res = ImageCopyResampled($new_img, $this->imageHandle, 0, 0, 0, 0, $new_x, $new_y, $this->img_x, $this->img_y);
406 ImageCopyResized($new_img, $this->imageHandle, 0, 0, 0, 0, $new_x, $new_y, $this->img_x, $this->img_y);
408 $this->oldImage = $this->imageHandle;
409 $this->imageHandle = $new_img;
410 $this->resized = true;
412 $this->new_x = $new_x;
413 $this->new_y = $new_y;
418 * Adjusts the image gamma
420 * @param float $outputgamma
422 * @return bool|PEAR_Error TRUE or a PEAR_Error object on error
425 function gamma($outputgamma = 1.0)
427 if ($outputgamma != 1.0) {
428 ImageGammaCorrect($this->imageHandle, 1.0, $outputgamma);
434 * Helper method to save to a file or output the image
436 * @param string $filename the name of the file to write to (blank to output)
437 * @param string $types define the output format, default
438 * is the current used format
439 * @param int $quality output DPI, default is 75
441 * @return bool|PEAR_Error TRUE on success or PEAR_Error object on error
444 function _generate($filename, $type = '', $quality = null)
446 $type = strtolower(($type == '') ? $this->type : $type);
447 $options = (is_array($quality)) ? $quality : array();
452 if (is_numeric($quality)) {
453 $options['quality'] = $quality;
455 $quality = $this->_getOption('quality', $options, 75);
458 if (!$this->supportsType($type, 'w')) {
459 return PEAR::raiseError('Image type not supported for output',
460 IMAGE_TRANSFORM_ERROR_UNSUPPORTED);
463 if ($filename == '') {
464 header('Content-type: ' . $this->getMimeType($type));
465 $action = 'output image';
467 $action = 'save image to file';
470 $functionName = 'image' . $type;
473 $result = $functionName($this->imageHandle, $filename, $quality);
476 if ($filename == '') {
477 $result = $functionName($this->imageHandle);
479 $result = $functionName($this->imageHandle, $filename);
483 return PEAR::raiseError('Couldn\'t ' . $action,
484 IMAGE_TRANSFORM_ERROR_IO);
486 $this->imageHandle = $this->oldImage;
487 if (!$this->keep_settings_on_save) {
495 * Displays image without saving and lose changes.
497 * This method adds the Content-type HTTP header
499 * @param string $type (JPEG, PNG...);
500 * @param int $quality 75
502 * @return bool|PEAR_Error TRUE or PEAR_Error object on error
505 function display($type = '', $quality = null)
507 return $this->_generate('', $type, $quality);
511 * Saves the image to a file
513 * @param string $filename the name of the file to write to
514 * @param string $type the output format, default
515 * is the current used format
516 * @param int $quality default is 75
518 * @return bool|PEAR_Error TRUE on success or PEAR_Error object on error
521 function save($filename, $type = '', $quality = null)
523 if (!trim($filename)) {
524 return PEAR::raiseError('Filename missing',
525 IMAGE_TRANSFORM_ERROR_ARGUMENT);
527 return $this->_generate($filename, $type, $quality);
531 * Destroys image handle
537 $this->resized = false;
538 if (is_resource($this->imageHandle)) {
539 ImageDestroy($this->imageHandle);
541 $this->imageHandle = null;
542 if (is_resource($this->oldImage)){
543 ImageDestroy($this->oldImage);
545 $this->oldImage = null;
549 * Returns a new image for temporary processing
551 * @param int $width width of the new image
552 * @param int $height height of the new image
553 * @param bool $trueColor force which type of image to create
554 * @return resource a GD image resource
557 function _createImage($width = -1, $height = -1, $trueColor = null)
560 $width = $this->new_x;
563 $height = $this->new_y;
567 if (is_null($trueColor)) {
568 if (function_exists('imageistruecolor')) {
569 $createtruecolor = imageistruecolor($this->imageHandle);
571 $createtruecolor = true;
574 $createtruecolor = $trueColor;
577 && function_exists('ImageCreateTrueColor')) {
578 $new_img = @ImageCreateTrueColor($width, $height);
579 imagealphablending($new_img, false);
580 imagesavealpha($new_img, true);
583 $new_img = ImageCreate($width, $height);
584 imagepalettecopy($new_img, $this->imageHandle);
585 $color = imagecolortransparent($this->imageHandle);
587 imagecolortransparent($new_img, $color);
588 imagefill($new_img, 0, 0, $color);