1<?php 2 3/* vim: set expandtab tabstop=4 shiftwidth=4: */ 4 5/** 6 * GD implementation for Image_Transform package 7 * 8 * PHP versions 4 and 5 9 * 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. 15 * 16 * @category Image 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 322661 2012-01-24 12:02:59Z clockwerx $ 25 * @link http://pear.php.net/package/Image_Transform 26 */ 27 28require_once 'Image/Transform.php'; 29 30/** 31 * GD implementation for Image_Transform package 32 * 33 * Usage : 34 * $img =& Image_Transform::factory('GD'); 35 * $angle = -78; 36 * $img->load('magick.png'); 37 * 38 * if ($img->rotate($angle, array( 39 * 'autoresize' => true, 40 * 'color_mask' => array(255, 0, 0)))) { 41 * $img->addText(array( 42 * 'text' => 'Rotation ' . $angle, 43 * 'x' => 0, 44 * 'y' => 100, 45 * 'font' => '/usr/share/fonts/default/TrueType/cogb____.ttf')); 46 * $img->display(); 47 * } else { 48 * echo "Error"; 49 * } 50 * $img->free(); 51 * 52 * @category Image 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 62 * @since PHP 4.0 63 */ 64class Image_Transform_Driver_GD extends Image_Transform 65{ 66 /** 67 * Holds the image resource for manipulation 68 * 69 * @var resource $imageHandle 70 * @access protected 71 */ 72 var $imageHandle = null; 73 74 /** 75 * Holds the original image file 76 * 77 * @var resource $imageHandle 78 * @access protected 79 */ 80 var $oldImage = null; 81 82 /** 83 * Check settings 84 */ 85 function Image_Transform_Driver_GD() 86 { 87 $this->__construct(); 88 } // End function Image 89 90 /** 91 * Check settings 92 * 93 * @since PHP 5 94 */ 95 function __construct() 96 { 97 if (!PEAR::loadExtension('gd')) { 98 $this->isError(PEAR::raiseError("GD library is not available.", 99 IMAGE_TRANSFORM_ERROR_UNSUPPORTED)); 100 } else { 101 $types = ImageTypes(); 102 if ($types & IMG_PNG) { 103 $this->_supported_image_types['png'] = 'rw'; 104 } 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'] = 'rw'; 110 } 111 if ($types & IMG_JPG) { 112 $this->_supported_image_types['jpeg'] = 'rw'; 113 } 114 if ($types & IMG_WBMP) { 115 $this->_supported_image_types['wbmp'] = 'rw'; 116 } 117 if (!$this->_supported_image_types) { 118 $this->isError(PEAR::raiseError("No supported image types available", IMAGE_TRANSFORM_ERROR_UNSUPPORTED)); 119 } 120 } 121 122 } // End function Image 123 124 /** 125 * Loads an image from file 126 * 127 * @param string $image filename 128 * @return bool|PEAR_Error TRUE or a PEAR_Error object on error 129 * @access public 130 */ 131 function load($image) 132 { 133 $this->free(); 134 135 $this->image = $image; 136 $result = $this->_get_image_details($image); 137 if (PEAR::isError($result)) { 138 return $result; 139 } 140 if (!$this->supportsType($this->type, 'r')) { 141 return PEAR::raiseError('Image type not supported for input', 142 IMAGE_TRANSFORM_ERROR_UNSUPPORTED); 143 } 144 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); 151 } 152 return true; 153 154 } // End load 155 156 /** 157 * Returns the GD image handle 158 * 159 * @return resource 160 * 161 * @access public 162 */ 163 function getHandle() 164 { 165 return $this->imageHandle; 166 }//function getHandle() 167 168 /** 169 * Adds a border of constant width around an image 170 * 171 * @param int $border_width Width of border to add 172 * @author Peter Bowyer 173 * @return bool TRUE 174 * @access public 175 */ 176 function addBorder($border_width, $color = '') 177 { 178 $this->new_x = $this->img_x + 2 * $border_width; 179 $this->new_y = $this->img_y + 2 * $border_width; 180 181 $new_img = $this->_createImage($new_x, $new_y, $this->true_color); 182 183 $options = array('pencilColor', $color); 184 $color = $this->_getColor('pencilColor', $options, array(0, 0, 0)); 185 if ($color) { 186 if ($this->true_color) { 187 $c = imagecolorresolve($this->imageHandle, $color[0], $color[1], $color[2]); 188 imagefill($new_img, 0, 0, $c); 189 } else { 190 imagecolorset($new_img, imagecolorat($new_img, 0, 0), $color[0], $color[1], $color[2]); 191 } 192 } 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; 196 197 return true; 198 } 199 200 /** 201 * addText 202 * 203 * @param array $params Array contains options 204 * array( 205 * 'text' The string to draw 206 * 'x' Horizontal position 207 * 'y' Vertical Position 208 * 'color' Font color 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 213 * ) 214 * 215 * @return bool|PEAR_Error TRUE or a PEAR_Error object on error 216 */ 217 function addText($params) 218 { 219 $this->oldImage = $this->imageHandle; 220 $params = array_merge($this->_get_default_text_params(), $params); 221 extract($params); 222 223 $options = array('fontColor' => $color); 224 $color = $this->_getColor('fontColor', $options, array(0, 0, 0)); 225 226 $c = imagecolorresolve ($this->imageHandle, $color[0], $color[1], $color[2]); 227 228 if ('ttf' == substr($font, -3)) { 229 ImageTTFText($this->imageHandle, $size, $angle, $x, $y, $c, $font, $text); 230 } else { 231 ImagePSText($this->imageHandle, $size, $angle, $x, $y, $c, $font, $text); 232 } 233 234 return true; 235 } // End addText 236 237 /** 238 * Rotates image by the given angle 239 * 240 * Uses a fast rotation algorythm for custom angles 241 * or lines copy for multiple of 90 degrees 242 * 243 * @param int $angle Rotation angle 244 * @param array $options array( 245 * 'canvasColor' => array(r ,g, b), named color or #rrggbb 246 * ) 247 * @author Pierre-Alain Joye 248 * @return bool|PEAR_Error TRUE or a PEAR_Error object on error 249 * @access public 250 */ 251 function rotate($angle, $options = null) 252 { 253 if (($angle % 360) == 0) { 254 return true; 255 } 256 257 $color_mask = $this->_getColor('canvasColor', $options, 258 array(255, 255, 255)); 259 260 $mask = imagecolorresolve($this->imageHandle, $color_mask[0], $color_mask[1], $color_mask[2]); 261 262 $this->oldImage = $this->imageHandle; 263 264 // Multiply by -1 to change the sign, so the image is rotated clockwise 265 $this->imageHandle = ImageRotate($this->imageHandle, $angle * -1, $mask); 266 return true; 267 } 268 269 /** 270 * Horizontal mirroring 271 * 272 * @return mixed TRUE or PEAR_Error object on error 273 * @access public 274 * @see flip() 275 **/ 276 function mirror() 277 { 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); 282 } 283 imagedestroy($this->imageHandle); 284 $this->imageHandle = $new_img; 285 return true; 286 } 287 288 /** 289 * Vertical mirroring 290 * 291 * @return TRUE or PEAR Error object on error 292 * @access public 293 * @see mirror() 294 **/ 295 function flip() 296 { 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); 301 } 302 imagedestroy($this->imageHandle); 303 $this->imageHandle = $new_img; 304 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); 314 } 315 } */ 316 return true; 317 } 318 319 /** 320 * Crops image by size and start coordinates 321 * 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 327 * @access public 328 */ 329 function crop($width, $height, $x = 0, $y = 0) 330 { 331 // Sanity check 332 if (!$this->intersects($width, $height, $x, $y)) { 333 return PEAR::raiseError('Nothing to crop', IMAGE_TRANSFORM_ERROR_OUTOFBOUND); 334 } 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); 340 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); 345 } 346 347 $this->oldImage = $this->imageHandle; 348 $this->imageHandle = $new_img; 349 $this->resized = true; 350 351 $this->new_x = $width; 352 $this->new_y = $height; 353 return true; 354 } 355 356 /** 357 * Converts the image to greyscale 358 * 359 * @return bool|PEAR_Error TRUE or a PEAR_Error object on error 360 * @access public 361 */ 362 function greyscale() { 363 imagecopymergegray($this->imageHandle, $this->imageHandle, 0, 0, 0, 0, $this->new_x, $this->new_y, 0); 364 return true; 365 } 366 367 /** 368 * Resize Action 369 * 370 * For GD 2.01+ the new copyresampled function is used 371 * It uses a bicubic interpolation algorithm to get far 372 * better result. 373 * 374 * @param int $new_x New width 375 * @param int $new_y New height 376 * @param array $options Optional parameters 377 * <ul> 378 * <li>'scaleMethod': "pixel" or "smooth"</li> 379 * </ul> 380 * 381 * @return bool|PEAR_Error TRUE on success or PEAR_Error object on error 382 * @access protected 383 */ 384 function _resize($new_x, $new_y, $options = null) 385 { 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); 388 } 389 390 if ($this->new_x == $new_x && $this->new_y == $new_y) { 391 return true; 392 } 393 394 $scaleMethod = $this->_getOption('scaleMethod', $options, 'smooth'); 395 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); 400 401 $icr_res = null; 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); 404 } 405 if (!$icr_res) { 406 ImageCopyResized($new_img, $this->imageHandle, 0, 0, 0, 0, $new_x, $new_y, $this->img_x, $this->img_y); 407 } 408 $this->oldImage = $this->imageHandle; 409 $this->imageHandle = $new_img; 410 $this->resized = true; 411 412 $this->new_x = $new_x; 413 $this->new_y = $new_y; 414 return true; 415 } 416 417 /** 418 * Adjusts the image gamma 419 * 420 * @param float $outputgamma 421 * 422 * @return bool|PEAR_Error TRUE or a PEAR_Error object on error 423 * @access public 424 */ 425 function gamma($outputgamma = 1.0) 426 { 427 if ($outputgamma != 1.0) { 428 ImageGammaCorrect($this->imageHandle, 1.0, $outputgamma); 429 } 430 return true; 431 } 432 433 /** 434 * Helper method to save to a file or output the image 435 * 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 440 * 441 * @return bool|PEAR_Error TRUE on success or PEAR_Error object on error 442 * @access protected 443 */ 444 function _generate($filename, $type = '', $quality = null) 445 { 446 $type = strtolower(($type == '') ? $this->type : $type); 447 $options = (is_array($quality)) ? $quality : array(); 448 switch ($type) { 449 case 'jpg': 450 $type = 'jpeg'; 451 case 'jpeg': 452 if (is_numeric($quality)) { 453 $options['quality'] = $quality; 454 } 455 $quality = $this->_getOption('quality', $options, 75); 456 break; 457 } 458 if (!$this->supportsType($type, 'w')) { 459 return PEAR::raiseError('Image type not supported for output', 460 IMAGE_TRANSFORM_ERROR_UNSUPPORTED); 461 } 462 463 if ($filename == '') { 464 header('Content-type: ' . $this->getMimeType($type)); 465 $action = 'output image'; 466 } else { 467 $action = 'save image to file'; 468 } 469 470 $functionName = 'image' . $type; 471 switch ($type) { 472 case 'jpeg': 473 $result = $functionName($this->imageHandle, $filename, $quality); 474 break; 475 default: 476 if ($filename == '') { 477 $result = $functionName($this->imageHandle); 478 } else { 479 $result = $functionName($this->imageHandle, $filename); 480 } 481 } 482 if (!$result) { 483 return PEAR::raiseError('Couldn\'t ' . $action, 484 IMAGE_TRANSFORM_ERROR_IO); 485 } 486 $this->imageHandle = $this->oldImage; 487 if (!$this->keep_settings_on_save) { 488 $this->free(); 489 } 490 return true; 491 492 } // End save 493 494 /** 495 * Displays image without saving and lose changes. 496 * 497 * This method adds the Content-type HTTP header 498 * 499 * @param string $type (JPEG, PNG...); 500 * @param int $quality 75 501 * 502 * @return bool|PEAR_Error TRUE or PEAR_Error object on error 503 * @access public 504 */ 505 function display($type = '', $quality = null) 506 { 507 return $this->_generate('', $type, $quality); 508 } 509 510 /** 511 * Saves the image to a file 512 * 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 517 * 518 * @return bool|PEAR_Error TRUE on success or PEAR_Error object on error 519 * @access public 520 */ 521 function save($filename, $type = '', $quality = null) 522 { 523 if (!trim($filename)) { 524 return PEAR::raiseError('Filename missing', 525 IMAGE_TRANSFORM_ERROR_ARGUMENT); 526 } 527 return $this->_generate($filename, $type, $quality); 528 } 529 530 /** 531 * Destroys image handle 532 * 533 * @access public 534 */ 535 function free() 536 { 537 $this->resized = false; 538 if (is_resource($this->imageHandle)) { 539 ImageDestroy($this->imageHandle); 540 } 541 $this->imageHandle = null; 542 if (is_resource($this->oldImage)){ 543 ImageDestroy($this->oldImage); 544 } 545 $this->oldImage = null; 546 } 547 548 /** 549 * Returns a new image for temporary processing 550 * 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 555 * @access protected 556 */ 557 function _createImage($width = -1, $height = -1, $trueColor = null) 558 { 559 if ($width == -1) { 560 $width = $this->new_x; 561 } 562 if ($height == -1) { 563 $height = $this->new_y; 564 } 565 566 $new_img = null; 567 if (is_null($trueColor)) { 568 if (function_exists('imageistruecolor')) { 569 $createtruecolor = imageistruecolor($this->imageHandle); 570 } else { 571 $createtruecolor = true; 572 } 573 } else { 574 $createtruecolor = $trueColor; 575 } 576 if ($createtruecolor 577 && function_exists('ImageCreateTrueColor')) { 578 $new_img = @ImageCreateTrueColor($width, $height); 579 //GIF Transparent Patch 580 if ($this->type!='gif') { 581 imagealphablending($new_img, false); 582 imagesavealpha($new_img, true); 583 } 584 //End GIF Transparent Patch 585 } 586 if (!$new_img) { 587 $new_img = ImageCreate($width, $height); 588 imagepalettecopy($new_img, $this->imageHandle); 589 $color = imagecolortransparent($this->imageHandle); 590 if ($color != -1) { 591 imagecolortransparent($new_img, $color); 592 imagefill($new_img, 0, 0, $color); 593 } 594 } 595 596 //GIF Transparent Patch 597 if ($this->type=='gif') { 598 $transparencyIndex = imagecolortransparent($this->imageHandle); 599 $transparencyColor = array('red' => 255, 'green' => 255, 'blue' => 255); 600 601 if ($transparencyIndex >= 0) { 602 $transparencyColor = imagecolorsforindex($this->imageHandle, $transparencyIndex); 603 } 604 605 $transparencyIndex = imagecolorallocate($new_img, $transparencyColor['red'], $transparencyColor['green'], $transparencyColor['blue']); 606 imagefill($new_img, 0, 0, $transparencyIndex); 607 imagecolortransparent($new_img, $transparencyIndex); 608 } 609 //End GIF Transparent Patch 610 611 612 return $new_img; 613 } 614}