| [ Index ] |
PHP Cross Reference of MantisBT |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * File containing the ezcGraphGdDriver class 4 * 5 * @package Graph 6 * @version 1.5 7 * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved. 8 * @license http://ez.no/licenses/new_bsd New BSD License 9 */ 10 /** 11 * Driver using PHPs ext/gd to draw images. The GD extension is available on 12 * nearly all PHP installations, but slow and produces slightly incorrect 13 * results. 14 * 15 * The driver can make use of the different font extensions available with 16 * ext/gd. It is possible to use Free Type 2, native TTF and PostScript Type 1 17 * fonts. 18 * 19 * The options of this driver are configured in {@link ezcGraphGdDriverOptions} 20 * extending the basic driver options class {@link ezcGraphDriverOptions}. 21 * 22 * <code> 23 * $graph = new ezcGraphPieChart(); 24 * $graph->palette = new ezcGraphPaletteEzGreen(); 25 * $graph->title = 'Access statistics'; 26 * $graph->legend = false; 27 * 28 * $graph->driver = new ezcGraphGdDriver(); 29 * $graph->options->font = 'tutorial_font.ttf'; 30 * 31 * // Generate a Jpeg with lower quality. The default settings result in a image 32 * // with better quality. 33 * // 34 * // The reduction of the supersampling to 1 will result in no anti aliasing of 35 * // the image. JPEG is not the optimal format for grapics, PNG is far better for 36 * // this kind of images. 37 * $graph->driver->options->supersampling = 1; 38 * $graph->driver->options->jpegQuality = 100; 39 * $graph->driver->options->imageFormat = IMG_JPEG; 40 * 41 * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array( 42 * 'Mozilla' => 19113, 43 * 'Explorer' => 10917, 44 * 'Opera' => 1464, 45 * 'Safari' => 652, 46 * 'Konqueror' => 474, 47 * ) ); 48 * 49 * $graph->render( 400, 200, 'tutorial_dirver_gd.jpg' ); 50 * </code> 51 * 52 * @version 1.5 53 * @package Graph 54 * @mainclass 55 */ 56 class ezcGraphGdDriver extends ezcGraphDriver 57 { 58 59 /** 60 * Image resource 61 * 62 * @var resource 63 */ 64 protected $image; 65 66 /** 67 * Array with image files to draw 68 * 69 * @var array 70 */ 71 protected $preProcessImages = array(); 72 73 /** 74 * List of strings to draw 75 * array ( array( 76 * 'text' => array( 'strings' ), 77 * 'options' => ezcGraphFontOptions, 78 * ) 79 * 80 * @var array 81 */ 82 protected $strings = array(); 83 84 /** 85 * Contains resources for already loaded ps fonts. 86 * array( 87 * path => resource 88 * ) 89 * 90 * @var array 91 */ 92 protected $psFontResources = array(); 93 94 /** 95 * Constructor 96 * 97 * @param array $options Default option array 98 * @return void 99 * @ignore 100 */ 101 public function __construct( array $options = array() ) 102 { 103 ezcBase::checkDependency( 'Graph', ezcBase::DEP_PHP_EXTENSION, 'gd' ); 104 $this->options = new ezcGraphGdDriverOptions( $options ); 105 } 106 107 /** 108 * Returns the image resource to draw on. 109 * 110 * If no resource exists the image will be created. The size of the 111 * returned image depends on the supersampling factor and the size of the 112 * chart. 113 * 114 * @return resource 115 */ 116 protected function getImage() 117 { 118 if ( !isset( $this->image ) ) 119 { 120 $this->image = imagecreatetruecolor( 121 $this->supersample( $this->options->width ), 122 $this->supersample( $this->options->height ) 123 ); 124 125 // Default to a transparent white background 126 $bgColor = imagecolorallocatealpha( $this->image, 255, 255, 255, 127 ); 127 imagealphablending( $this->image, true ); 128 imagesavealpha( $this->image, true ); 129 imagefill( $this->image, 1, 1, $bgColor ); 130 131 imagesetthickness( 132 $this->image, 133 $this->options->supersampling 134 ); 135 } 136 137 return $this->image; 138 } 139 140 /** 141 * Allocates a color 142 * 143 * This function tries to allocate the requested color. If the color 144 * already exists in the imaga it will be reused. 145 * 146 * @param ezcGraphColor $color 147 * @return int Color index 148 */ 149 protected function allocate( ezcGraphColor $color ) 150 { 151 $image = $this->getImage(); 152 153 if ( $color->alpha > 0 ) 154 { 155 $fetched = imagecolorexactalpha( $image, $color->red, $color->green, $color->blue, $color->alpha / 2 ); 156 if ( $fetched < 0 ) 157 { 158 $fetched = imagecolorallocatealpha( $image, $color->red, $color->green, $color->blue, $color->alpha / 2 ); 159 } 160 return $fetched; 161 } 162 else 163 { 164 $fetched = imagecolorexact( $image, $color->red, $color->green, $color->blue ); 165 if ( $fetched < 0 ) 166 { 167 $fetched = imagecolorallocate( $image, $color->red, $color->green, $color->blue ); 168 } 169 return $fetched; 170 } 171 } 172 173 /** 174 * Creates an image resource from an image file 175 * 176 * @param string $file Filename 177 * @return resource Image 178 */ 179 protected function imageCreateFrom( $file ) 180 { 181 $data = getimagesize( $file ); 182 183 switch ( $data[2] ) 184 { 185 case 1: 186 return array( 187 'width' => $data[0], 188 'height' => $data[1], 189 'image' => imagecreatefromgif( $file ) 190 ); 191 case 2: 192 return array( 193 'width' => $data[0], 194 'height' => $data[1], 195 'image' => imagecreatefromjpeg( $file ) 196 ); 197 case 3: 198 return array( 199 'width' => $data[0], 200 'height' => $data[1], 201 'image' => imagecreatefrompng( $file ) 202 ); 203 default: 204 throw new ezcGraphGdDriverUnsupportedImageTypeException( $data[2] ); 205 } 206 } 207 208 /** 209 * Supersamples a single coordinate value. 210 * 211 * Applies supersampling to a single coordinate value. 212 * 213 * @param float $value Coordinate value 214 * @return float Supersampled coordinate value 215 */ 216 protected function supersample( $value ) 217 { 218 $mod = (int) floor( $this->options->supersampling / 2 ); 219 return $value * $this->options->supersampling - $mod; 220 } 221 222 /** 223 * Draws a single polygon. 224 * 225 * @param array $points Point array 226 * @param ezcGraphColor $color Polygon color 227 * @param mixed $filled Filled 228 * @param float $thickness Line thickness 229 * @return void 230 */ 231 public function drawPolygon( array $points, ezcGraphColor $color, $filled = true, $thickness = 1. ) 232 { 233 $image = $this->getImage(); 234 235 $drawColor = $this->allocate( $color ); 236 237 // Create point array 238 $pointCount = count( $points ); 239 $pointArray = array(); 240 for ( $i = 0; $i < $pointCount; ++$i ) 241 { 242 $pointArray[] = $this->supersample( $points[$i]->x ); 243 $pointArray[] = $this->supersample( $points[$i]->y ); 244 } 245 246 // Draw polygon 247 if ( $filled ) 248 { 249 imagefilledpolygon( $image, $pointArray, $pointCount, $drawColor ); 250 } 251 else 252 { 253 imagepolygon( $image, $pointArray, $pointCount, $drawColor ); 254 } 255 256 return $points; 257 } 258 259 /** 260 * Draws a line 261 * 262 * @param ezcGraphCoordinate $start Start point 263 * @param ezcGraphCoordinate $end End point 264 * @param ezcGraphColor $color Line color 265 * @param float $thickness Line thickness 266 * @return void 267 */ 268 public function drawLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color, $thickness = 1. ) 269 { 270 $image = $this->getImage(); 271 272 $drawColor = $this->allocate( $color ); 273 274 imagesetthickness( 275 $this->image, 276 $this->options->supersampling * $thickness 277 ); 278 279 imageline( 280 $image, 281 $this->supersample( $start->x ), 282 $this->supersample( $start->y ), 283 $this->supersample( $end->x ), 284 $this->supersample( $end->y ), 285 $drawColor 286 ); 287 288 imagesetthickness( 289 $this->image, 290 $this->options->supersampling 291 ); 292 293 return array(); 294 } 295 296 /** 297 * Returns boundings of text depending on the available font extension 298 * 299 * @param float $size Textsize 300 * @param ezcGraphFontOptions $font Font 301 * @param string $text Text 302 * @return ezcGraphBoundings Boundings of text 303 */ 304 protected function getTextBoundings( $size, ezcGraphFontOptions $font, $text ) 305 { 306 switch ( $font->type ) 307 { 308 case ezcGraph::PS_FONT: 309 if ( !isset( $this->psFontResources[$font->path] ) ) 310 { 311 $this->psFontResources[$font->path] = imagePsLoadFont( $font->path ); 312 } 313 314 $boundings = imagePsBBox( $text, $this->psFontResources[$font->path], $size ); 315 return new ezcGraphBoundings( 316 $boundings[0], 317 $boundings[1], 318 $boundings[2], 319 $boundings[3] 320 ); 321 case ezcGraph::TTF_FONT: 322 switch ( true ) 323 { 324 case ezcBaseFeatures::hasFunction( 'imageftbbox' ) && !$this->options->forceNativeTTF: 325 $boundings = imageFtBBox( $size, 0, $font->path, $text ); 326 return new ezcGraphBoundings( 327 $boundings[0], 328 $boundings[1], 329 $boundings[4], 330 $boundings[5] 331 ); 332 case ezcBaseFeatures::hasFunction( 'imagettfbbox' ): 333 $boundings = imageTtfBBox( $size, 0, $font->path, $text ); 334 return new ezcGraphBoundings( 335 $boundings[0], 336 $boundings[1], 337 $boundings[4], 338 $boundings[5] 339 ); 340 } 341 break; 342 } 343 } 344 345 /** 346 * Render text depending of font type and available font extensions 347 * 348 * @param resource $image Image resource 349 * @param string $text Text 350 * @param int $type Font type 351 * @param string $path Font path 352 * @param ezcGraphColor $color Font color 353 * @param ezcGraphCoordinate $position Position 354 * @param float $size Textsize 355 * @param ezcGraphRotation $rotation 356 * 357 * @return void 358 */ 359 protected function renderText( $image, $text, $type, $path, ezcGraphColor $color, ezcGraphCoordinate $position, $size, ezcGraphRotation $rotation = null ) 360 { 361 if ( $rotation !== null ) 362 { 363 // Rotation is relative to top left point of text and not relative 364 // to the bounding coordinate system 365 $rotation = new ezcGraphRotation( 366 $rotation->getRotation(), 367 new ezcGraphCoordinate( 368 $rotation->getCenter()->x - $position->x, 369 $rotation->getCenter()->y - $position->y 370 ) 371 ); 372 } 373 374 switch ( $type ) 375 { 376 case ezcGraph::PS_FONT: 377 imagePsText( 378 $image, 379 $text, 380 $this->psFontResources[$path], 381 $size, 382 $this->allocate( $color ), 383 1, 384 $position->x + 385 ( $rotation === null ? 0 : $rotation->get( 0, 2 ) ), 386 $position->y + 387 ( $rotation === null ? 0 : $rotation->get( 1, 2 ) ), 388 0, 389 0, 390 ( $rotation === null ? 0 : -$rotation->getRotation() ), 391 4 392 ); 393 break; 394 case ezcGraph::TTF_FONT: 395 switch ( true ) 396 { 397 case ezcBaseFeatures::hasFunction( 'imagefttext' ) && !$this->options->forceNativeTTF: 398 imageFtText( 399 $image, 400 $size, 401 ( $rotation === null ? 0 : -$rotation->getRotation() ), 402 $position->x + 403 ( $rotation === null ? 0 : $rotation->get( 0, 2 ) ), 404 $position->y + 405 ( $rotation === null ? 0 : $rotation->get( 1, 2 ) ), 406 $this->allocate( $color ), 407 $path, 408 $text 409 ); 410 break; 411 case ezcBaseFeatures::hasFunction( 'imagettftext' ): 412 imageTtfText( 413 $image, 414 $size, 415 ( $rotation === null ? 0 : -$rotation->getRotation() ), 416 $position->x + 417 ( $rotation === null ? 0 : $rotation->get( 0, 2 ) ), 418 $position->y + 419 ( $rotation === null ? 0 : $rotation->get( 1, 2 ) ), 420 $this->allocate( $color ), 421 $path, 422 $text 423 ); 424 break; 425 } 426 break; 427 } 428 } 429 430 /** 431 * Writes text in a box of desired size 432 * 433 * @param string $string Text 434 * @param ezcGraphCoordinate $position Top left position 435 * @param float $width Width of text box 436 * @param float $height Height of text box 437 * @param int $align Alignement of text 438 * @param ezcGraphRotation $rotation 439 * @return void 440 */ 441 public function drawTextBox( $string, ezcGraphCoordinate $position, $width, $height, $align, ezcGraphRotation $rotation = null ) 442 { 443 $padding = $this->options->font->padding + ( $this->options->font->border !== false ? $this->options->font->borderWidth : 0 ); 444 445 $width -= $padding * 2; 446 $height -= $padding * 2; 447 $position->x += $padding; 448 $position->y += $padding; 449 450 // Try to get a font size for the text to fit into the box 451 $maxSize = min( $height, $this->options->font->maxFontSize ); 452 $result = false; 453 for ( $size = $maxSize; $size >= $this->options->font->minFontSize; --$size ) 454 { 455 $result = $this->testFitStringInTextBox( $string, $position, $width, $height, $size ); 456 if ( is_array( $result ) ) 457 { 458 break; 459 } 460 $size = floor( ( $newsize = $size * ( $result ) ) >= $size ? $size - 1 : $newsize ); 461 } 462 463 if ( !is_array( $result ) ) 464 { 465 if ( ( $height >= $this->options->font->minFontSize ) && 466 ( $this->options->autoShortenString ) ) 467 { 468 $result = $this->tryFitShortenedString( $string, $position, $width, $height, $size = $this->options->font->minFontSize ); 469 } 470 else 471 { 472 throw new ezcGraphFontRenderingException( $string, $this->options->font->minFontSize, $width, $height ); 473 } 474 } 475 476 $this->options->font->minimalUsedFont = $size; 477 478 $this->strings[] = array( 479 'text' => $result, 480 'position' => $position, 481 'width' => $width, 482 'height' => $height, 483 'align' => $align, 484 'font' => $this->options->font, 485 'rotation' => $rotation, 486 ); 487 488 return array( 489 clone $position, 490 new ezcGraphCoordinate( $position->x + $width, $position->y ), 491 new ezcGraphCoordinate( $position->x + $width, $position->y + $height ), 492 new ezcGraphCoordinate( $position->x, $position->y + $height ), 493 ); 494 } 495 496 /** 497 * Draw all collected texts 498 * 499 * The texts are collected and their maximum possible font size is 500 * calculated. This function finally draws the texts on the image, this 501 * delayed drawing has two reasons: 502 * 503 * 1) This way the text strings are always on top of the image, what 504 * results in better readable texts 505 * 2) The maximum possible font size can be calculated for a set of texts 506 * with the same font configuration. Strings belonging to one chart 507 * element normally have the same font configuration, so that all texts 508 * belonging to one element will have the same font size. 509 * 510 * @access protected 511 * @return void 512 */ 513 protected function drawAllTexts() 514 { 515 $image = $this->getImage(); 516 517 foreach ( $this->strings as $text ) 518 { 519 $size = $text['font']->minimalUsedFont; 520 521 $completeHeight = count( $text['text'] ) * $size + ( count( $text['text'] ) - 1 ) * $this->options->lineSpacing; 522 523 // Calculate y offset for vertical alignement 524 switch ( true ) 525 { 526 case ( $text['align'] & ezcGraph::BOTTOM ): 527 $yOffset = $text['height'] - $completeHeight; 528 break; 529 case ( $text['align'] & ezcGraph::MIDDLE ): 530 $yOffset = ( $text['height'] - $completeHeight ) / 2; 531 break; 532 case ( $text['align'] & ezcGraph::TOP ): 533 default: 534 $yOffset = 0; 535 break; 536 } 537 538 $padding = $text['font']->padding + $text['font']->borderWidth / 2; 539 if ( $this->options->font->minimizeBorder === true ) 540 { 541 // Calculate maximum width of text rows 542 $width = false; 543 foreach ( $text['text'] as $line ) 544 { 545 $string = implode( ' ', $line ); 546 $boundings = $this->getTextBoundings( $size, $text['font'], $string ); 547 if ( ( $width === false) || ( $boundings->width > $width ) ) 548 { 549 $width = $boundings->width; 550 } 551 } 552 553 switch ( true ) 554 { 555 case ( $text['align'] & ezcGraph::LEFT ): 556 $xOffset = 0; 557 break; 558 case ( $text['align'] & ezcGraph::CENTER ): 559 $xOffset = ( $text['width'] - $width ) / 2; 560 break; 561 case ( $text['align'] & ezcGraph::RIGHT ): 562 $xOffset = $text['width'] - $width; 563 break; 564 } 565 566 $borderPolygonArray = array( 567 new ezcGraphCoordinate( 568 $text['position']->x - $padding + $xOffset, 569 $text['position']->y - $padding + $yOffset 570 ), 571 new ezcGraphCoordinate( 572 $text['position']->x + $padding * 2 + $xOffset + $width, 573 $text['position']->y - $padding + $yOffset 574 ), 575 new ezcGraphCoordinate( 576 $text['position']->x + $padding * 2 + $xOffset + $width, 577 $text['position']->y + $padding * 2 + $yOffset + $completeHeight 578 ), 579 new ezcGraphCoordinate( 580 $text['position']->x - $padding + $xOffset, 581 $text['position']->y + $padding * 2 + $yOffset + $completeHeight 582 ), 583 ); 584 } 585 else 586 { 587 $borderPolygonArray = array( 588 new ezcGraphCoordinate( 589 $text['position']->x - $padding, 590 $text['position']->y - $padding 591 ), 592 new ezcGraphCoordinate( 593 $text['position']->x + $padding * 2 + $text['width'], 594 $text['position']->y - $padding 595 ), 596 new ezcGraphCoordinate( 597 $text['position']->x + $padding * 2 + $text['width'], 598 $text['position']->y + $padding * 2 + $text['height'] 599 ), 600 new ezcGraphCoordinate( 601 $text['position']->x - $padding, 602 $text['position']->y + $padding * 2 + $text['height'] 603 ), 604 ); 605 } 606 607 if ( $text['rotation'] !== null ) 608 { 609 foreach ( $borderPolygonArray as $nr => $point ) 610 { 611 $borderPolygonArray[$nr] = $text['rotation']->transformCoordinate( $point ); 612 } 613 } 614 615 if ( $text['font']->background !== false ) 616 { 617 $this->drawPolygon( 618 $borderPolygonArray, 619 $text['font']->background, 620 true 621 ); 622 } 623 624 if ( $text['font']->border !== false ) 625 { 626 $this->drawPolygon( 627 $borderPolygonArray, 628 $text['font']->border, 629 false, 630 $text['font']->borderWidth 631 ); 632 } 633 634 // Render text with evaluated font size 635 foreach ( $text['text'] as $line ) 636 { 637 $string = implode( ' ', $line ); 638 $boundings = $this->getTextBoundings( $size, $text['font'], $string ); 639 $text['position']->y += $size; 640 641 switch ( true ) 642 { 643 case ( $text['align'] & ezcGraph::LEFT ): 644 $position = new ezcGraphCoordinate( 645 $text['position']->x, 646 $text['position']->y + $yOffset 647 ); 648 break; 649 case ( $text['align'] & ezcGraph::RIGHT ): 650 $position = new ezcGraphCoordinate( 651 $text['position']->x + ( $text['width'] - $boundings->width ), 652 $text['position']->y + $yOffset 653 ); 654 break; 655 case ( $text['align'] & ezcGraph::CENTER ): 656 $position = new ezcGraphCoordinate( 657 $text['position']->x + ( ( $text['width'] - $boundings->width ) / 2 ), 658 $text['position']->y + $yOffset 659 ); 660 break; 661 } 662 663 // Calculate relative modification of rotation center point 664 if ( $text['rotation'] !== null ) 665 { 666 $rotation = new ezcGraphRotation( 667 $text['rotation']->getRotation(), 668 new ezcGraphCoordinate( 669 $text['rotation']->getCenter()->x + 670 $position->x - $text['position']->x, 671 $text['rotation']->getCenter()->y + 672 $position->y - $text['position']->y 673 ) 674 ); 675 $rotation = $text['rotation']; 676 } 677 else 678 { 679 $rotation = null; 680 } 681 682 // Optionally draw text shadow 683 if ( $text['font']->textShadow === true ) 684 { 685 $this->renderText( 686 $image, 687 $string, 688 $text['font']->type, 689 $text['font']->path, 690 $text['font']->textShadowColor, 691 new ezcGraphCoordinate( 692 $position->x + $text['font']->textShadowOffset, 693 $position->y + $text['font']->textShadowOffset 694 ), 695 $size, 696 $rotation 697 ); 698 } 699 700 // Finally draw text 701 $this->renderText( 702 $image, 703 $string, 704 $text['font']->type, 705 $text['font']->path, 706 $text['font']->color, 707 $position, 708 $size, 709 $rotation 710 ); 711 712 $text['position']->y += $size * $this->options->lineSpacing; 713 } 714 } 715 } 716 717 /** 718 * Draws a sector of cirlce 719 * 720 * @param ezcGraphCoordinate $center Center of circle 721 * @param mixed $width Width 722 * @param mixed $height Height 723 * @param mixed $startAngle Start angle of circle sector 724 * @param mixed $endAngle End angle of circle sector 725 * @param ezcGraphColor $color Color 726 * @param mixed $filled Filled 727 * @return void 728 */ 729 public function drawCircleSector( ezcGraphCoordinate $center, $width, $height, $startAngle, $endAngle, ezcGraphColor $color, $filled = true ) 730 { 731 $image = $this->getImage(); 732 $drawColor = $this->allocate( $color ); 733 734 // Normalize angles 735 if ( $startAngle > $endAngle ) 736 { 737 $tmp = $startAngle; 738 $startAngle = $endAngle; 739 $endAngle = $tmp; 740 } 741 742 if ( ( $endAngle - $startAngle ) > 359.99999 ) 743 { 744 return $this->drawCircle( $center, $width, $height, $color, $filled ); 745 } 746 747 // Because of bug #45552 in PHPs ext/GD we check for a minimal distance 748 // on the outer border of the circle sector, and skip the drawing if 749 // the distance is lower then 1. 750 // 751 // See also: http://bugs.php.net/45552 752 $startPoint = new ezcGraphVector( 753 $center->x + 754 ( ( cos( deg2rad( $startAngle ) ) * $width ) / 2 ), 755 $center->y + 756 ( ( sin( deg2rad( $startAngle ) ) * $height ) / 2 ) 757 ); 758 if ( $startPoint->sub( new ezcGraphVector( 759 $center->x + 760 ( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ), 761 $center->y + 762 ( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 ) 763 ) )->length() < 1 ) 764 { 765 // Skip this circle sector 766 return array(); 767 } 768 769 if ( $filled ) 770 { 771 imagefilledarc( 772 $image, 773 $this->supersample( $center->x ), 774 $this->supersample( $center->y ), 775 $this->supersample( $width ), 776 $this->supersample( $height ), 777 $startAngle, 778 $endAngle, 779 $drawColor, 780 IMG_ARC_PIE 781 ); 782 } 783 else 784 { 785 imagefilledarc( 786 $image, 787 $this->supersample( $center->x ), 788 $this->supersample( $center->y ), 789 $this->supersample( $width ), 790 $this->supersample( $height ), 791 $startAngle, 792 $endAngle, 793 $drawColor, 794 IMG_ARC_PIE | IMG_ARC_NOFILL | IMG_ARC_EDGED 795 ); 796 } 797 798 // Create polygon array to return 799 $polygonArray = array( $center ); 800 for ( $angle = $startAngle; $angle < $endAngle; $angle += $this->options->imageMapResolution ) 801 { 802 $polygonArray[] = new ezcGraphCoordinate( 803 $center->x + 804 ( ( cos( deg2rad( $angle ) ) * $width ) / 2 ), 805 $center->y + 806 ( ( sin( deg2rad( $angle ) ) * $height ) / 2 ) 807 ); 808 } 809 $polygonArray[] = new ezcGraphCoordinate( 810 $center->x + 811 ( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ), 812 $center->y + 813 ( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 ) 814 ); 815 816 return $polygonArray; 817 } 818 819 /** 820 * Draws a single element of a circular arc 821 * 822 * ext/gd itself does not support something like circular arcs, so that 823 * this functions draws rectangular polygons as a part of circular arcs 824 * to interpolate them. This way it is possible to apply a linear gradient 825 * to the circular arc, because we draw single steps anyway. 826 * 827 * @param ezcGraphCoordinate $center Center of ellipse 828 * @param integer $width Width of ellipse 829 * @param integer $height Height of ellipse 830 * @param integer $size Height of border 831 * @param float $startAngle Starting angle of circle sector 832 * @param float $endAngle Ending angle of circle sector 833 * @param ezcGraphColor $color Color of Border 834 * @return void 835 */ 836 protected function drawCircularArcStep( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color ) 837 { 838 $this->drawPolygon( 839 array( 840 new ezcGraphCoordinate( 841 $center->x + 842 ( ( cos( deg2rad( $startAngle ) ) * $width ) / 2 ), 843 $center->y + 844 ( ( sin( deg2rad( $startAngle ) ) * $height ) / 2 ) 845 ), 846 new ezcGraphCoordinate( 847 $center->x + 848 ( ( cos( deg2rad( $startAngle ) ) * $width ) / 2 ), 849 $center->y + 850 ( ( sin( deg2rad( $startAngle ) ) * $height ) / 2 ) + $size 851 ), 852 new ezcGraphCoordinate( 853 $center->x + 854 ( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ), 855 $center->y + 856 ( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 ) + $size 857 ), 858 new ezcGraphCoordinate( 859 $center->x + 860 ( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ), 861 $center->y + 862 ( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 ) 863 ), 864 ), 865 $color->darken( $this->options->shadeCircularArc * ( 1 + cos ( deg2rad( $startAngle ) ) ) / 2 ), 866 true 867 ); 868 } 869 870 /** 871 * Draws a circular arc 872 * 873 * @param ezcGraphCoordinate $center Center of ellipse 874 * @param integer $width Width of ellipse 875 * @param integer $height Height of ellipse 876 * @param integer $size Height of border 877 * @param float $startAngle Starting angle of circle sector 878 * @param float $endAngle Ending angle of circle sector 879 * @param ezcGraphColor $color Color of Border 880 * @param bool $filled 881 * @return void 882 */ 883 public function drawCircularArc( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color, $filled = true ) 884 { 885 $image = $this->getImage(); 886 $drawColor = $this->allocate( $color ); 887 888 // Normalize angles 889 if ( $startAngle > $endAngle ) 890 { 891 $tmp = $startAngle; 892 $startAngle = $endAngle; 893 $endAngle = $tmp; 894 } 895 896 if ( $filled === true ) 897 { 898 $startIteration = ceil( $startAngle / $this->options->detail ) * $this->options->detail; 899 $endIteration = floor( $endAngle / $this->options->detail ) * $this->options->detail; 900 901 if ( $startAngle < $startIteration ) 902 { 903 // Draw initial step 904 $this->drawCircularArcStep( 905 $center, 906 $width, 907 $height, 908 $size, 909 $startAngle, 910 $startIteration, 911 $color 912 ); 913 } 914 915 // Draw all steps 916 for ( ; $startIteration < $endIteration; $startIteration += $this->options->detail ) 917 { 918 $this->drawCircularArcStep( 919 $center, 920 $width, 921 $height, 922 $size, 923 $startIteration, 924 $startIteration + $this->options->detail, 925 $color 926 ); 927 } 928 929 if ( $endIteration < $endAngle ) 930 { 931 // Draw closing step 932 $this->drawCircularArcStep( 933 $center, 934 $width, 935 $height, 936 $size, 937 $endIteration, 938 $endAngle, 939 $color 940 ); 941 } 942 } 943 else 944 { 945 imagefilledarc( 946 $image, 947 $this->supersample( $center->x ), 948 $this->supersample( $center->y ), 949 $this->supersample( $width ), 950 $this->supersample( $height ), 951 $startAngle, 952 $endAngle, 953 $drawColor, 954 IMG_ARC_PIE | IMG_ARC_NOFILL 955 ); 956 } 957 958 // Create polygon array to return 959 $polygonArray = array(); 960 for ( $angle = $startAngle; $angle < $endAngle; $angle += $this->options->imageMapResolution ) 961 { 962 $polygonArray[] = new ezcGraphCoordinate( 963 $center->x + 964 ( ( cos( deg2rad( $angle ) ) * $width ) / 2 ), 965 $center->y + 966 ( ( sin( deg2rad( $angle ) ) * $height ) / 2 ) 967 ); 968 } 969 $polygonArray[] = new ezcGraphCoordinate( 970 $center->x + 971 ( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ), 972 $center->y + 973 ( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 ) 974 ); 975 976 for ( $angle = $endAngle; $angle > $startAngle; $angle -= $this->options->imageMapResolution ) 977 { 978 $polygonArray[] = new ezcGraphCoordinate( 979 $center->x + 980 ( ( cos( deg2rad( $angle ) ) * $width ) / 2 ) + $size, 981 $center->y + 982 ( ( sin( deg2rad( $angle ) ) * $height ) / 2 ) 983 ); 984 } 985 $polygonArray[] = new ezcGraphCoordinate( 986 $center->x + 987 ( ( cos( deg2rad( $startAngle ) ) * $width ) / 2 ) + $size, 988 $center->y + 989 ( ( sin( deg2rad( $startAngle ) ) * $height ) / 2 ) 990 ); 991 992 return $polygonArray; 993 } 994 995 /** 996 * Draw circle 997 * 998 * @param ezcGraphCoordinate $center Center of ellipse 999 * @param mixed $width Width of ellipse 1000 * @param mixed $height height of ellipse 1001 * @param ezcGraphColor $color Color 1002 * @param mixed $filled Filled 1003 * @return void 1004 */ 1005 public function drawCircle( ezcGraphCoordinate $center, $width, $height, ezcGraphColor $color, $filled = true ) 1006 { 1007 $image = $this->getImage(); 1008 1009 $drawColor = $this->allocate( $color ); 1010 1011 if ( $filled ) 1012 { 1013 imagefilledellipse( 1014 $image, 1015 $this->supersample( $center->x ), 1016 $this->supersample( $center->y ), 1017 $this->supersample( $width ), 1018 $this->supersample( $height ), 1019 $drawColor 1020 ); 1021 } 1022 else 1023 { 1024 imageellipse( 1025 $image, 1026 $this->supersample( $center->x ), 1027 $this->supersample( $center->y ), 1028 $this->supersample( $width ), 1029 $this->supersample( $height ), 1030 $drawColor 1031 ); 1032 } 1033 1034 $polygonArray = array(); 1035 for ( $angle = 0; $angle < 360; $angle += $this->options->imageMapResolution ) 1036 { 1037 $polygonArray[] = new ezcGraphCoordinate( 1038 $center->x + 1039 ( ( cos( deg2rad( $angle ) ) * $width ) / 2 ), 1040 $center->y + 1041 ( ( sin( deg2rad( $angle ) ) * $height ) / 2 ) 1042 ); 1043 } 1044 1045 return $polygonArray; 1046 } 1047 1048 /** 1049 * Draw an image 1050 * 1051 * The actual drawing of the image is delayed, to not apply supersampling 1052 * to the image. The image will normally be resized using the gd function 1053 * imagecopyresampled, which provides nice antialiased scaling, so that 1054 * additional supersampling would make the image look blurred. The delayed 1055 * images will be pre-processed, so that they are draw in the back of 1056 * everything else. 1057 * 1058 * @param mixed $file Image file 1059 * @param ezcGraphCoordinate $position Top left position 1060 * @param mixed $width Width of image in destination image 1061 * @param mixed $height Height of image in destination image 1062 * @return void 1063 */ 1064 public function drawImage( $file, ezcGraphCoordinate $position, $width, $height ) 1065 { 1066 $this->preProcessImages[] = array( 1067 'file' => $file, 1068 'position' => clone $position, 1069 'width' => $width, 1070 'height' => $height, 1071 ); 1072 1073 return array( 1074 $position, 1075 new ezcGraphCoordinate( $position->x + $width, $position->y ), 1076 new ezcGraphCoordinate( $position->x + $width, $position->y + $height ), 1077 new ezcGraphCoordinate( $position->x, $position->y + $height ), 1078 ); 1079 } 1080 1081 /** 1082 * Draw all images to image resource handler 1083 * 1084 * @param resource $image Image to draw on 1085 * @return resource Updated image resource 1086 */ 1087 protected function addImages( $image ) 1088 { 1089 foreach ( $this->preProcessImages as $preImage ) 1090 { 1091 $preImageData = $this->imageCreateFrom( $preImage['file'] ); 1092 call_user_func_array( 1093 $this->options->resampleFunction, 1094 array( 1095 $image, 1096 $preImageData['image'], 1097 $preImage['position']->x, $preImage['position']->y, 1098 0, 0, 1099 $preImage['width'], $preImage['height'], 1100 $preImageData['width'], $preImageData['height'], 1101 ) 1102 ); 1103 } 1104 1105 return $image; 1106 } 1107 1108 /** 1109 * Return mime type for current image format 1110 * 1111 * @return string 1112 */ 1113 public function getMimeType() 1114 { 1115 switch ( $this->options->imageFormat ) 1116 { 1117 case IMG_PNG: 1118 return 'image/png'; 1119 case IMG_JPEG: 1120 return 'image/jpeg'; 1121 } 1122 } 1123 1124 /** 1125 * Render image directly to output 1126 * 1127 * The method renders the image directly to the standard output. You 1128 * normally do not want to use this function, because it makes it harder 1129 * to proper cache the generated graphs. 1130 * 1131 * @return void 1132 */ 1133 public function renderToOutput() 1134 { 1135 header( 'Content-Type: ' . $this->getMimeType() ); 1136 $this->render( null ); 1137 } 1138 1139 /** 1140 * Finally save image 1141 * 1142 * @param string $file Destination filename 1143 * @return void 1144 */ 1145 public function render( $file ) 1146 { 1147 $destination = imagecreatetruecolor( $this->options->width, $this->options->height ); 1148 1149 // Default to a transparent white background 1150 $bgColor = imagecolorallocatealpha( $destination, 255, 255, 255, 127 ); 1151 imagealphablending( $destination, true ); 1152 imagesavealpha( $destination, true ); 1153 imagefill( $destination, 1, 1, $bgColor ); 1154 1155 // Apply background if one is defined 1156 if ( $this->options->background !== false ) 1157 { 1158 $background = $this->imageCreateFrom( $this->options->background ); 1159 1160 call_user_func_array( 1161 $this->options->resampleFunction, 1162 array( 1163 $destination, 1164 $background['image'], 1165 0, 0, 1166 0, 0, 1167 $this->options->width, $this->options->height, 1168 $background['width'], $background['height'], 1169 ) 1170 ); 1171 } 1172 1173 // Draw all images to exclude them from supersampling 1174 $destination = $this->addImages( $destination ); 1175 1176 // Finally merge with graph 1177 $image = $this->getImage(); 1178 call_user_func_array( 1179 $this->options->resampleFunction, 1180 array( 1181 $destination, 1182 $image, 1183 0, 0, 1184 0, 0, 1185 $this->options->width, $this->options->height, 1186 $this->supersample( $this->options->width ), $this->supersample( $this->options->height ) 1187 ) 1188 ); 1189 1190 $this->image = $destination; 1191 imagedestroy( $image ); 1192 1193 // Draw all texts 1194 // Reset supersampling during text rendering 1195 $supersampling = $this->options->supersampling; 1196 $this->options->supersampling = 1; 1197 $this->drawAllTexts(); 1198 $this->options->supersampling = $supersampling; 1199 1200 $image = $this->getImage(); 1201 switch ( $this->options->imageFormat ) 1202 { 1203 case IMG_PNG: 1204 if ( $file === null ) 1205 { 1206 imagepng( $image ); 1207 } 1208 else 1209 { 1210 imagepng( $image, $file ); 1211 } 1212 break; 1213 case IMG_JPEG: 1214 imagejpeg( $image, $file, $this->options->jpegQuality ); 1215 break; 1216 default: 1217 throw new ezcGraphGdDriverUnsupportedImageTypeException( $this->options->imageFormat ); 1218 } 1219 } 1220 1221 /** 1222 * Get resource of rendered result 1223 * 1224 * Return the resource of the rendered result. You should not use this 1225 * method before you called either renderToOutput() or render(), as the 1226 * image may not be completely rendered until then. 1227 * 1228 * @return resource 1229 */ 1230 public function getResource() 1231 { 1232 return $this->image; 1233 } 1234 } 1235 1236 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Jul 28 15:48:31 2011 | Cross-referenced by PHPXref 0.7 |