1<?php 2 3/* vim: set expandtab tabstop=4 shiftwidth=4: */ 4 5/** 6 * NetPBM 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 * @author Peter Bowyer <peter@mapledesign.co.uk> 19 * @author Philippe Jausions <Philippe.Jausions@11abacus.com> 20 * @copyright 2002-2005 The PHP Group 21 * @license http://www.php.net/license/3_0.txt PHP License 3.0 22 * @version CVS: $Id: NetPBM.php 236527 2007-05-28 16:36:09Z dufuz $ 23 * @link http://pear.php.net/package/Image_Transform 24 */ 25 26require_once 'Image/Transform.php'; 27require_once 'System.php'; 28 29/** 30 * NetPBM implementation for Image_Transform package 31 * 32 * @category Image 33 * @package Image_Transform 34 * @subpackage Image_Transform_Driver_NetPBM 35 * @author Peter Bowyer <peter@mapledesign.co.uk> 36 * @author Philippe Jausions <Philippe.Jausions@11abacus.com> 37 * @copyright 2002-2005 The PHP Group 38 * @license http://www.php.net/license/3_0.txt PHP License 3.0 39 * @version Release: @package_version@ 40 * @link http://pear.php.net/package/Image_Transform 41 * @link http://netpbm.sourceforge.net/ 42 */ 43class Image_Transform_Driver_NetPBM extends Image_Transform 44{ 45 /** 46 * associative array commands to be executed 47 * @var array 48 */ 49 var $command = array(); 50 51 /** 52 * Class Constructor 53 */ 54 function Image_Transform_Driver_NetPBM() 55 { 56 $this->__construct(); 57 58 } // End function Image_NetPBM 59 60 /** 61 * Class Constructor 62 */ 63 function __construct() 64 { 65 if (!defined('IMAGE_TRANSFORM_NETPBM_PATH')) { 66 $path = dirname(System::which('pnmscale')) 67 . DIRECTORY_SEPARATOR; 68 define('IMAGE_TRANSFORM_NETPBM_PATH', $path); 69 } 70 if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH . 'pnmscale' 71 . ((OS_WINDOWS) ? '.exe' : ''))) { 72 $this->isError(PEAR::raiseError('Couldn\'t find "pnmscale" binary', 73 IMAGE_TRANSFORM_ERROR_UNSUPPORTED)); 74 } 75 } // End function Image_NetPBM 76 77 /** 78 * Load image 79 * 80 * @param string filename 81 * @return bool|PEAR_Error TRUE or a PEAR_Error object on error 82 * @access public 83 */ 84 function load($image) 85 { 86 $this->image = $image; 87 $result = $this->_get_image_details($image); 88 if (PEAR::isError($result)) { 89 return $result; 90 } 91 return true; 92 93 } // End load 94 95 /** 96 * Resize the image. 97 * 98 * @access private 99 * 100 * @param int $new_x New width 101 * @param int $new_y New height 102 * @param mixed $options Optional parameters 103 * 104 * @return true on success or PEAR Error object on error 105 * @see PEAR::isError() 106 */ 107 function _resize($new_x, $new_y, $options = null) 108 { 109 // there's no technical reason why resize can't be called multiple 110 // times...it's just silly to do so 111 $scaleMethod = $this->_getOption('scaleMethod', $options, 'smooth'); 112 switch ($scaleMethod) { 113 case 'pixel': 114 $scale_x = $new_x / $this->img_x; 115 if ($scale_x == $new_y / $this->img_x 116 && $scale_x > 1 117 && floor($scale_x) == $scale_x) { 118 if (System::which(IMAGE_TRANSFORM_NETPBM_PATH . 119 'pnmenlarge' 120 . ((OS_WINDOWS) ? '.exe' : ''))) { 121 $this->command[] = $this->_prepare_cmd( 122 IMAGE_TRANSFORM_NETPBM_PATH, 123 'pnmenlarge', 124 $scale_x); 125 } else { 126 return PEAR::raiseError('Couldn\'t find "pnmenlarge" binary', 127 IMAGE_TRANSFORM_ERROR_UNSUPPORTED); 128 } 129 } else { 130 $this->command[] = $this->_prepare_cmd( 131 IMAGE_TRANSFORM_NETPBM_PATH, 132 'pnmscale', 133 '-nomix -width ' . ((int) $new_x) 134 . ' -height ' . ((int) $new_y)); 135 } 136 break; 137 138 case 'smooth': 139 default: 140 $this->command[] = $this->_prepare_cmd( 141 IMAGE_TRANSFORM_NETPBM_PATH, 142 'pnmscale', 143 '-width ' . ((int) $new_x) . ' -height ' 144 . ((int) $new_y)); 145 // Smooth things if scaling by a factor more than 3 146 // (see pnmscale man page) 147 if ($new_x / $this->img_x > 3 148 || $new_y / $this->img_y > 3) { 149 if (System::which(IMAGE_TRANSFORM_NETPBM_PATH . 150 'pnmsmooth' . ((OS_WINDOWS) ? '.exe' : ''))) { 151 $this->command[] = $this->_prepare_cmd( 152 IMAGE_TRANSFORM_NETPBM_PATH, 153 'pnmsmooth'); 154 } else { 155 return PEAR::raiseError('Couldn\'t find "pnmsmooth" binary', 156 IMAGE_TRANSFORM_ERROR_UNSUPPORTED); 157 } 158 } 159 } // End [SWITCH] 160 161 $this->_set_new_x($new_x); 162 $this->_set_new_y($new_y); 163 return true; 164 165 } // End resize 166 167 /** 168 * Rotates the image 169 * 170 * @param int $angle The angle to rotate the image through 171 * @param array $options 172 * @return bool|PEAR_Error TRUE on success, PEAR_Error object on error 173 */ 174 function rotate($angle, $options = null) 175 { 176 if (!($angle == $this->_rotation_angle($angle))) { 177 // No rotation needed 178 return true; 179 } 180 181 // For pnmrotate, we want to limit rotations from -45 to +45 degrees 182 // even if acceptable range is -90 to +90 (see pnmrotate man page) 183 // Bring image to that range by using pamflip 184 if ($angle > 45 && $angle < 315) { 185 if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH . 186 'pamflip' . ((OS_WINDOWS) ? '.exe' : ''))) { 187 return PEAR::raiseError('Couldn\'t find "pamflip" binary', 188 IMAGE_TRANSFORM_ERROR_UNSUPPORTED); 189 } 190 191 $quarters = floor(ceil($angle / 45) / 2); 192 $this->command[] = $this->_prepare_cmd( 193 IMAGE_TRANSFORM_NETPBM_PATH, 194 'pamflip', 195 '-rotate' . (360 - $quarters * 90)); 196 $angle -= $quarters * 90; 197 } 198 199 if ($angle != 0) { 200 if ($angle > 45) { 201 $angle -= 360; 202 } 203 204 if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH . 205 'pnmrotate' . ((OS_WINDOWS) ? '.exe' : ''))) { 206 return PEAR::raiseError('Couldn\'t find "pnmrotate" binary', 207 IMAGE_TRANSFORM_ERROR_UNSUPPORTED); 208 } 209 210 $bgcolor = $this->_getColor('canvasColor', $options, 211 array(255, 255, 255)); 212 $bgcolor = $this->colorarray2colorhex($bgcolor); 213 214 $scaleMethod = $this->_getOption('scaleMethod', $options, 'smooth'); 215 if ($scaleMethod != 'pixel') { 216 $this->command[] = $this->_prepare_cmd( 217 IMAGE_TRANSFORM_NETPBM_PATH, 218 'pnmrotate', 219 '-background=' . $bgcolor . ' -' . (float) $angle); 220 } else { 221 $this->command[] = $this->_prepare_cmd( 222 IMAGE_TRANSFORM_NETPBM_PATH, 223 'pnmrotate', 224 '-background=' . $bgcolor . ' -noantialias -' . (float) $angle); 225 } 226 } 227 return true; 228 } // End rotate 229 230 /** 231 * Crop an image 232 * 233 * @param int $width Cropped image width 234 * @param int $height Cropped image height 235 * @param int $x positive X-coordinate to crop at 236 * @param int $y positive Y-coordinate to crop at 237 * 238 * @return mixed TRUE or a PEAR error object on error 239 * @todo keep track of the new cropped size 240 **/ 241 function crop($width, $height, $x = 0, $y = 0) 242 { 243 // Sanity check 244 if (!$this->intersects($width, $height, $x, $y)) { 245 return PEAR::raiseError('Nothing to crop', IMAGE_TRANSFORM_ERROR_OUTOFBOUND); 246 } 247 if ($x != 0 || $y != 0 248 || $width != $this->img_x 249 || $height != $this->img_y) { 250 if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH . 251 'pnmcut' . ((OS_WINDOWS) ? '.exe' : ''))) { 252 return PEAR::raiseError('Couldn\'t find "pnmcut" binary', 253 IMAGE_TRANSFORM_ERROR_UNSUPPORTED); 254 } 255 256 $this->command[] = $this->_prepare_cmd( 257 IMAGE_TRANSFORM_NETPBM_PATH, 258 'pnmcut', 259 '-left ' . ((int) $x) 260 . ' -top ' . ((int) $y) 261 . ' -width ' . ((int) $width) 262 . ' -height ' . ((int) $height)); 263 } 264 return true; 265 } // End crop 266 267 /** 268 * Adjust the image gamma 269 * 270 * @param float $outputgamma 271 * 272 * @return mixed TRUE or a PEAR error object on error 273 */ 274 function gamma($outputgamma = 1.0) { 275 if ($outputgamme != 1.0) { 276 if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH . 277 'pnmgamma' . ((OS_WINDOWS) ? '.exe' : ''))) { 278 return PEAR::raiseError('Couldn\'t find "pnmgamma" binary', 279 IMAGE_TRANSFORM_ERROR_UNSUPPORTED); 280 } 281 $this->command[] = $this->_prepare_cmd( 282 IMAGE_TRANSFORM_NETPBM_PATH, 283 'pnmgamma', 284 (float) $outputgamma); 285 } 286 return true; 287 } 288 289 /** 290 * Vertical mirroring 291 * 292 * @see mirror() 293 * @return TRUE or PEAR Error object on error 294 **/ 295 function flip() 296 { 297 if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH . 298 'pamflip' . ((OS_WINDOWS) ? '.exe' : ''))) { 299 return PEAR::raiseError('Couldn\'t find "pamflip" binary', 300 IMAGE_TRANSFORM_ERROR_UNSUPPORTED); 301 } 302 $this->command[] = $this->_prepare_cmd( 303 IMAGE_TRANSFORM_NETPBM_PATH, 304 'pamflip', 305 '-topbottom'); 306 return true; 307 } 308 309 /** 310 * Horizontal mirroring 311 * 312 * @see flip() 313 * @return TRUE or PEAR Error object on error 314 **/ 315 function mirror() 316 { 317 if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH . 318 'pamflip' . ((OS_WINDOWS) ? '.exe' : ''))) { 319 return PEAR::raiseError('Couldn\'t find "pamflip" binary', 320 IMAGE_TRANSFORM_ERROR_UNSUPPORTED); 321 } 322 $this->command[] = $this->_prepare_cmd( 323 IMAGE_TRANSFORM_NETPBM_PATH, 324 'pamflip', 325 '-leftright'); 326 return true; 327 } 328 329 /** 330 * Converts an image into greyscale colors 331 * 332 * @access public 333 * @return mixed TRUE or a PEAR error object on error 334 **/ 335 function greyscale() 336 { 337 if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH . 338 'ppmtopgm' . ((OS_WINDOWS) ? '.exe' : ''))) { 339 return PEAR::raiseError('Couldn\'t find "ppmtopgm" binary', 340 IMAGE_TRANSFORM_ERROR_UNSUPPORTED); 341 } 342 $this->command[] = $this->_prepare_cmd( 343 IMAGE_TRANSFORM_NETPBM_PATH, 344 'ppmtopgm'); 345 return true; 346 } 347 348 /** 349 * adds text to an image 350 * 351 * @param array options Array contains options 352 * array( 353 * 'text' // The string to draw 354 * 'x' // Horizontal position 355 * 'y' // Vertical Position 356 * 'color' // Font color 357 * 'font' // Font to be used 358 * 'size' // Size of the fonts in pixel 359 * 'resize_first' // Tell if the image has to be resized 360 * // before drawing the text 361 * ) 362 * 363 * @return void 364 */ 365 function addText($params) 366 { 367 if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH . 368 'ppmlabel' . ((OS_WINDOWS) ? '.exe' : ''))) { 369 return PEAR::raiseError('Couldn\'t find "ppmlabel" binary', 370 IMAGE_TRANSFORM_ERROR_UNSUPPORTED); 371 } 372 373 // we ignore 'resize_first' since the more logical approach would be 374 // for the user to just call $this->_resize() _first_ ;) 375 extract(array_merge($this->_get_default_text_params(), $params)); 376 377 $options = array('colorFont' => $color); 378 $color = $this->_getColor('colorFont', $options, array(0, 0, 0)); 379 $color = $this->colorarray2colorhex($color); 380 381 $this->command[] = $this->_prepare_cmd( 382 IMAGE_TRANSFORM_NETPBM_PATH, 383 'ppmlabel', 384 '-angle ' . ((int) $angle) 385 . ' -colour ' . escapeshellarg($color) 386 . ' -size ' . ((float) $size) 387 . ' -x ' . ((int) $x) 388 . ' -y ' . ((int) ($y + $size)) 389 . ' -text ' . escapeshellarg($text)); 390 391 } // End addText 392 393 /** 394 * Image_Transform_Driver_NetPBM::_postProcess() 395 * 396 * @param $type 397 * @param $quality 398 * @return string A chain of shell command 399 * @link http://netpbm.sourceforge.net/doc/directory.html 400 */ 401 function _postProcess($type, $quality) 402 { 403 array_unshift($this->command, $this->_prepare_cmd( 404 IMAGE_TRANSFORM_NETPBM_PATH, 405 strtolower($this->type) . 'topnm', 406 escapeshellarg($this->image))); 407 $arg = ''; 408 $type = strtolower($type); 409 $program = ''; 410 switch ($type) { 411 // ppmto* converters 412 case 'gif': 413 if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH . 'ppmquant' 414 . ((OS_WINDOWS) ? '.exe' : ''))) { 415 return PEAR::raiseError('Couldn\'t find "ppmquant" binary', 416 IMAGE_TRANSFORM_ERROR_UNSUPPORTED); 417 } 418 $this->command[] = $this->_prepare_cmd( 419 IMAGE_TRANSFORM_NETPBM_PATH, 420 'ppmquant', 421 256); 422 case 'acad': 423 case 'bmp': 424 case 'eyuv': 425 case 'ilbm': 426 case 'leaf': 427 case 'lj': 428 case 'mitsu': 429 case 'mpeg': 430 case 'neo': 431 case 'pcx': 432 case 'pi1': 433 case 'pict': 434 case 'pj': 435 case 'pjxl': 436 case 'puzz': 437 case 'sixel': 438 case 'tga': 439 case 'uil': 440 case 'xpm': 441 case 'yuv': 442 $program = 'ppmto' . $type; 443 break; 444 445 // Windows icon 446 case 'winicon': 447 case 'ico': 448 $type = 'winicon'; 449 $program = 'ppmto' . $type; 450 break; 451 452 // pbmto* converters 453 case 'ascii': 454 case 'text': 455 case 'txt': 456 $type = 'ascii'; 457 case 'atk': 458 case 'bbubg': 459 case 'epsi': 460 case 'epson': 461 case 'escp2': 462 case 'icon': // Sun icon 463 case 'gem': 464 case 'go': 465 case 'lj': 466 case 'ln03': 467 case 'lps': 468 case 'macp': 469 case 'mda': 470 case 'mgr': 471 case 'pi3': 472 case 'pk': 473 case 'plot': 474 case 'ptx': 475 case 'wbp': 476 case 'xbm': 477 case 'x10bm': 478 case 'ybm': 479 case 'zinc': 480 case '10x': 481 $program = 'pbmto' . $type; 482 break; 483 484 // pamto* converters 485 case 'jpc': 486 $type = 'jpeg2k'; 487 case 'html': 488 case 'pfm': 489 case 'tga': 490 $program = 'pamto' . $type; 491 break; 492 493 // pnmto* converters 494 case 'jpc': 495 $type = 'jpeg2k'; 496 break; 497 case 'wfa': 498 $type = 'fiasco'; 499 break; 500 case 'jpg': 501 $type = 'jpeg'; 502 case 'jpeg': 503 $arg = '--quality=' . $quality; 504 case 'jbig': 505 case 'fits': 506 case 'palm': 507 case 'pclxl': 508 case 'png': 509 case 'ps': 510 case 'rast': 511 case 'rle': 512 case 'sgi': 513 case 'sir': 514 case 'tiff': 515 case 'xwd': 516 $program = 'pnmto' . $type; 517 break; 518 519 } // switch 520 521 if ($program == '') { 522 $program = 'pnmto' . $type; 523 } 524 525 if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH . $program 526 . ((OS_WINDOWS) ? '.exe' : ''))) { 527 return PEAR::raiseError("Couldn't find \"$program\" binary", 528 IMAGE_TRANSFORM_ERROR_UNSUPPORTED); 529 } 530 $this->command[] = $this->_prepare_cmd( 531 IMAGE_TRANSFORM_NETPBM_PATH, 532 $program); 533 return implode('|', $this->command); 534 } 535 536 /** 537 * Save the image file 538 * 539 * @param $filename string the name of the file to write to 540 * @param string $type (jpeg,png...); 541 * @param int $quality 75 542 * @return TRUE or PEAR Error object on error 543 */ 544 function save($filename, $type = null, $quality = 75) 545 { 546 $type = (is_null($type)) ? $this->type : $type; 547 $options = array(); 548 if (!is_null($quality)) { 549 $options['quality'] = $quality; 550 } 551 $quality = $this->_getOption('quality', $options, $quality); 552 553 $nullDevice = (OS_WINDOWS) ? 'nul' : '/dev/null'; 554 555 $cmd = $this->_postProcess($type, $quality) . '> "' . $filename . '"'; 556 exec($cmd . ' 2> ' . $nullDevice, $res, $exit); 557 if (!$this->keep_settings_on_save) { 558 $this->free(); 559 } 560 561 return ($exit == 0) ? true : PEAR::raiseError(implode('. ', $res), 562 IMAGE_TRANSFORM_ERROR_IO); 563 } // End save 564 565 /** 566 * Display image without saving and lose changes 567 * 568 * @param string $type (jpeg,png...); 569 * @param int $quality 75 570 * @return TRUE or PEAR Error object on error 571 */ 572 function display($type = null, $quality = null) 573 { 574 $type = (is_null($type)) ? $this->type : $type; 575 $options = array(); 576 if (!is_null($quality)) { 577 $options['quality'] = $quality; 578 } 579 $quality = $this->_getOption('quality', $options, 75); 580 581 header('Content-type: ' . $this->getMimeType($type)); 582 $cmd = $this->_postProcess($type, $quality); 583 passthru($cmd . ' 2>&1'); 584 if (!$this->keep_settings_on_save) { 585 $this->free(); 586 } 587 588 return true; 589 } 590 591 /** 592 * Destroy image handle 593 * 594 * @return void 595 */ 596 function free() 597 { 598 $this->command = array(); 599 } 600} // End class ImageIM