[ Index ]

PHP Cross Reference of MantisBT

title

Body

[close]

/library/ezc/Graph/src/driver/ -> gd.php (source)

   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  ?>


Generated: Thu Jul 28 15:48:31 2011 Cross-referenced by PHPXref 0.7