1<?php 2 3class GDCanvas implements ICanvas { 4 public function __construct($xSize, $ySize, $transparent=true) { 5 $this->picture = imagecreatetruecolor($xSize, $ySize); 6 7 $C_White = $this->allocateColor(new Color(255, 255, 255)); 8 imagefilledrectangle($this->picture, 0, 0, $xSize, $ySize, $C_White); 9 if($transparent) imagecolortransparent($this->picture, $C_White); 10 11 $this->antialiasQuality = 0; 12 } 13 14 function drawFilledRectangle(Point $corner1, Point $corner2, Color $color, 15 ShadowProperties $shadowProperties, $drawBorder = false, 16 $alpha = 100, $lineWidth = 1, $lineDotSize = 0) { 17 if($corner2->getX() < $corner1->getX()) { 18 $newCorner1 = new Point($corner2->getX(), $corner1->getY()); 19 $newCorner2 = new Point($corner1->getX(), $corner2->getY()); 20 21 $corner1 = $newCorner1; 22 $corner2 = $newCorner2; 23 } 24 25 if($corner2->getY() < $corner1->getY()) { 26 $newCorner1 = new Point($corner1->getX(), $corner2->getY()); 27 $newCorner2 = new Point($corner2->getX(), $corner1->getY()); 28 29 $corner1 = $newCorner1; 30 $corner2 = $newCorner2; 31 } 32 33 $X1 = $corner1->getX(); 34 $Y1 = $corner1->getY(); 35 36 $X2 = $corner2->getX(); 37 $Y2 = $corner2->getY(); 38 39 if($alpha == 100) { 40 /* Process shadows */ 41 if($shadowProperties->active) { 42 $this->drawFilledRectangle( 43 new Point($X1 + $shadowProperties->xDistance, 44 $Y1 + $shadowProperties->yDistance), 45 new Point($X2 + $shadowProperties->xDistance, 46 $Y2 + $shadowProperties->yDistance), 47 $shadowProperties->color, 48 ShadowProperties::NoShadow(), 49 FALSE, 50 $shadowProperties->alpha 51 ); 52 if($shadowProperties->blur != 0) { 53 $AlphaDecay = ($shadowProperties->alpha / $shadowProperties->blur); 54 55 for($i = 1; $i <= $shadowProperties->blur; $i++) 56 $this->drawFilledRectangle( 57 new Point($X1 + $shadowProperties->xDistance - $i / 2, 58 $Y1 + $shadowProperties->yDistance - $i / 2), 59 new Point($X2 + $shadowProperties->xDistance - $i / 2, 60 $Y2 + $shadowProperties->yDistance - $i / 2), 61 $shadowProperties->color, 62 ShadowProperties::NoShadow(), 63 FALSE, 64 $shadowProperties->alpha - $AlphaDecay * $i 65 ); 66 for($i = 1; $i <= $shadowProperties->blur; $i++) 67 $this->drawFilledRectangle( 68 new Point($X1 + $shadowProperties->xDistance + $i / 2, 69 $Y1 + $shadowProperties->yDistance + $i / 2), 70 new Point($X2 + $shadowProperties->xDistance + $i / 2, 71 $Y2 + $shadowProperties->xDistance + $i / 2), 72 $shadowProperties->color, 73 ShadowProperties::NoShadow(), 74 FALSE, 75 $shadowProperties->alpha - $AlphaDecay * $i 76 ); 77 } 78 } 79 80 $C_Rectangle = $this->allocateColor($color); 81 imagefilledrectangle($this->picture, round($X1), round($Y1), round($X2), round($Y2), $C_Rectangle); 82 } else { 83 $LayerWidth = abs($X2 - $X1) + 2; 84 $LayerHeight = abs($Y2 - $Y1) + 2; 85 86 $this->Layers [0] = imagecreatetruecolor($LayerWidth, $LayerHeight); 87 $C_White = imagecolorallocate($this->Layers [0], 255, 255, 255); 88 imagefilledrectangle($this->Layers [0], 0, 0, $LayerWidth, $LayerHeight, $C_White); 89 imagecolortransparent($this->Layers [0], $C_White); 90 91 $C_Rectangle = imagecolorallocate($this->Layers [0], $color->r, $color->g, $color->b); 92 imagefilledrectangle($this->Layers [0], round(1), round(1), round($LayerWidth - 1), round($LayerHeight - 1), $C_Rectangle); 93 94 imagecopymerge($this->picture, $this->Layers [0], round(min($X1, $X2) - 1), round(min($Y1, $Y2) - 1), 0, 0, $LayerWidth, $LayerHeight, $alpha); 95 imagedestroy($this->Layers [0]); 96 } 97 98 if($drawBorder) { 99 $this->drawRectangle( 100 new Point($X1, $Y1), 101 new Point($X2, $Y2), 102 $color, 103 $lineWidth, 104 $lineDotSize, 105 ShadowProperties::NoShadow() 106 ); 107 } 108 } 109 110 public function drawRectangle(Point $corner1, Point $corner2, Color $color, $lineWidth, $lineDotSize, ShadowProperties $shadowProperties) { 111 $X1 = $corner1->getX() - .2; 112 $Y1 = $corner1->getY() - .2; 113 $X2 = $corner2->getX() + .2; 114 $Y2 = $corner2->getY() + .2; 115 $this->drawLine( 116 new Point($X1, $Y1), 117 new Point($X2, $Y1), 118 $color, 119 $lineWidth, 120 $lineDotSize, 121 $shadowProperties 122 ); 123 124 $this->drawLine( 125 new Point($X2, $Y1), 126 new Point($X2, $Y2), 127 $color, 128 $lineWidth, 129 $lineDotSize, 130 $shadowProperties 131 ); 132 133 $this->drawLine( 134 new Point($X2, $Y2), 135 new Point($X1, $Y2), 136 $color, 137 $lineWidth, 138 $lineDotSize, 139 $shadowProperties 140 ); 141 142 $this->drawLine( 143 new Point($X1, $Y2), 144 new Point($X1, $Y1), 145 $color, 146 $lineWidth, 147 $lineDotSize, 148 $shadowProperties 149 ); 150 151 } 152 153 public function drawRoundedRectangle(Point $point1, Point $point2, $radius, Color $color, $lineWidth, $lineDotSize, ShadowProperties $shadowProperties) { 154 $Step = 90 / ((M_PI * $radius) / 2); 155 156 for($i = 0; $i <= 90; $i = $i + $Step) { 157 $X = cos(($i + 180) * M_PI / 180) * $radius + $point1->getX() + $radius; 158 $Y = sin(($i + 180) * M_PI / 180) * $radius + $point1->getY() + $radius; 159 $this->drawAntialiasPixel(new Point($X, $Y), $color, $shadowProperties); 160 161 $X = cos(($i - 90) * M_PI / 180) * $radius + $point2->getX() - $radius; 162 $Y = sin(($i - 90) * M_PI / 180) * $radius + $point1->getY() + $radius; 163 $this->drawAntialiasPixel(new Point($X, $Y), $color, $shadowProperties); 164 165 $X = cos(($i) * M_PI / 180) * $radius + $point2->getX() - $radius; 166 $Y = sin(($i) * M_PI / 180) * $radius + $point2->getY() - $radius; 167 $this->drawAntialiasPixel(new Point($X, $Y), $color, $shadowProperties); 168 169 $X = cos(($i + 90) * M_PI / 180) * $radius + $point1->getX() + $radius; 170 $Y = sin(($i + 90) * M_PI / 180) * $radius + $point2->getY() - $radius; 171 $this->drawAntialiasPixel(new Point($X, $Y), $color, $shadowProperties); 172 } 173 174 $X1 = $point1->getX() - .2; 175 $Y1 = $point1->getY() - .2; 176 $X2 = $point2->getX() + .2; 177 $Y2 = $point2->getY() + .2; 178 $this->drawLine( 179 new Point($X1 + $radius, $Y1), 180 new Point($X2 - $radius, $Y1), 181 $color, 182 $lineWidth, 183 $lineDotSize, 184 $shadowProperties 185 ); 186 187 $this->drawLine( 188 new Point($X2, $Y1 + $radius), 189 new Point($X2, $Y2 - $radius), 190 $color, 191 $lineWidth, 192 $lineDotSize, 193 $shadowProperties 194 ); 195 196 $this->drawLine( 197 new Point($X2 - $radius, $Y2), 198 new Point($X1 + $radius, $Y2), 199 $color, 200 $lineWidth, 201 $lineDotSize, 202 $shadowProperties 203 ); 204 205 $this->drawLine( 206 new Point($X1, $Y2 - $radius), 207 new Point($X1, $Y1 + $radius), 208 $color, 209 $lineWidth, 210 $lineDotSize, 211 $shadowProperties 212 ); 213 } 214 215 /** 216 * This function creates a filled rectangle with rounded corners 217 * and antialiasing 218 */ 219 function drawFilledRoundedRectangle(Point $point1, Point $point2, $radius, 220 Color $color, $lineWidth, $lineDotSize, 221 ShadowProperties $shadowProperties) { 222 $C_Rectangle = $this->allocateColor($color); 223 224 $Step = 90 / ((M_PI * $radius) / 2); 225 226 for($i = 0; $i <= 90; $i = $i + $Step) { 227 $Xi1 = cos(($i + 180) * M_PI / 180) * $radius 228 + $point1->getX() 229 + $radius; 230 231 $Yi1 = sin(($i + 180) * M_PI / 180) * $radius 232 + $point1->getY() 233 + $radius; 234 235 $Xi2 = cos(($i - 90) * M_PI / 180) * $radius 236 + $point2->getX() 237 - $radius; 238 239 $Yi2 = sin(($i - 90) * M_PI / 180) * $radius 240 + $point1->getY() 241 + $radius; 242 243 $Xi3 = cos(($i) * M_PI / 180) * $radius 244 + $point2->getX() 245 - $radius; 246 247 $Yi3 = sin(($i) * M_PI / 180) * $radius 248 + $point2->getY() 249 - $radius; 250 251 $Xi4 = cos(($i + 90) * M_PI / 180) * $radius 252 + $point1->getX() 253 + $radius; 254 255 $Yi4 = sin(($i + 90) * M_PI / 180) * $radius 256 + $point2->getY() 257 - $radius; 258 259 imageline( 260 $this->picture, 261 $Xi1, $Yi1, 262 $point1->getX() + $radius, $Yi1, 263 $C_Rectangle 264 ); 265 266 imageline( 267 $this->picture, $point2->getX() - $radius, $Yi2, 268 $Xi2, $Yi2, 269 $C_Rectangle 270 ); 271 272 imageline( 273 $this->picture, 274 $point2->getX() - $radius, $Yi3, 275 $Xi3, $Yi3, 276 $C_Rectangle 277 ); 278 279 imageline( 280 $this->picture, 281 $Xi4, $Yi4, 282 $point1->getX() + $radius, $Yi4, 283 $C_Rectangle 284 ); 285 286 $this->drawAntialiasPixel( 287 new Point($Xi1, $Yi1), 288 $color, 289 $shadowProperties 290 ); 291 $this->drawAntialiasPixel( 292 new Point($Xi2, $Yi2), 293 $color, 294 $shadowProperties 295 ); 296 $this->drawAntialiasPixel( 297 new Point($Xi3, $Yi3), 298 $color, 299 $shadowProperties 300 ); 301 $this->drawAntialiasPixel( 302 new Point($Xi4, $Yi4), 303 $color, 304 $shadowProperties 305 ); 306 } 307 308 imagefilledrectangle( 309 $this->picture, 310 $point1->getX(), $point1->getY() + $radius, 311 $point2->getX(), $point2->getY() - $radius, 312 $C_Rectangle 313 ); 314 315 imagefilledrectangle( 316 $this->picture, 317 $point1->getX() + $radius, $point1->getY(), 318 $point2->getX() - $radius, $point2->getY(), 319 $C_Rectangle 320 ); 321 322 $X1 = $point1->getX() - .2; 323 $Y1 = $point1->getY() - .2; 324 $X2 = $point2->getX() + .2; 325 $Y2 = $point2->getY() + .2; 326 $this->drawLine( 327 new Point($X1 + $radius, $Y1), 328 new Point($X2 - $radius, $Y1), 329 $color, 330 $lineWidth, $lineDotSize, 331 $shadowProperties 332 ); 333 334 $this->drawLine( 335 new Point($X2, $Y1 + $radius), 336 new Point($X2, $Y2 - $radius), 337 $color, 338 $lineWidth, $lineDotSize, 339 $shadowProperties 340 ); 341 342 $this->drawLine( 343 new Point($X2 - $radius, $Y2), 344 new Point($X1 + $radius, $Y2), 345 $color, 346 $lineWidth, $lineDotSize, 347 $shadowProperties 348 ); 349 350 $this->drawLine( 351 new Point($X1, $Y2 - $radius), 352 new Point($X1, $Y1 + $radius), 353 $color, 354 $lineWidth, $lineDotSize, 355 $shadowProperties 356 ); 357 358 } 359 360 public function drawLine(Point $point1, Point $point2, Color $color, $lineWidth, $lineDotSize, ShadowProperties $shadowProperties, Point $boundingBoxMin = null, Point $boundingBoxMax = null) { 361 if($lineDotSize > 1) { 362 $this->drawDottedLine( 363 $point1, 364 $point2, 365 $lineDotSize, $lineWidth, 366 $color, $shadowProperties, 367 $boundingBoxMin, 368 $boundingBoxMax 369 ); 370 return; 371 } 372 373 $Distance = $point1->distanceFrom($point2); 374 if($Distance == 0) 375 return; 376 $XStep = ($point2->getX() - $point1->getX()) / $Distance; 377 $YStep = ($point2->getY() - $point1->getY()) / $Distance; 378 379 for($i = 0; $i <= $Distance; $i++) { 380 $X = $i * $XStep + $point1->getX(); 381 $Y = $i * $YStep + $point1->getY(); 382 383 if((($boundingBoxMin == null) || (($X >= $boundingBoxMin->getX()) 384 && ($Y >= $boundingBoxMin->getY()))) 385 && (($boundingBoxMax == null) || (($X <= $boundingBoxMax->getX()) 386 && ($Y <= $boundingBoxMax->getY()))) 387 ) { 388 if($lineWidth == 1) 389 $this->drawAntialiasPixel(new Point($X, $Y), $color, $shadowProperties); 390 else { 391 $StartOffset = -($lineWidth / 2); 392 $EndOffset = ($lineWidth / 2); 393 for($j = $StartOffset; $j <= $EndOffset; $j++) 394 $this->drawAntialiasPixel( 395 new Point($X + $j, $Y + $j), 396 $color, $shadowProperties 397 ); 398 } 399 } 400 } 401 } 402 403 public function drawDottedLine(Point $point1, Point $point2, $dotSize, $lineWidth, Color $color, ShadowProperties $shadowProperties, Point $boundingBoxMin = null, Point $boundingBoxMax = null) { 404 $Distance = $point1->distanceFrom($point2); 405 406 $XStep = ($point2->getX() - $point1->getX()) / $Distance; 407 $YStep = ($point2->getY() - $point1->getY()) / $Distance; 408 409 $DotIndex = 0; 410 for($i = 0; $i <= $Distance; $i++) { 411 $X = $i * $XStep + $point1->getX(); 412 $Y = $i * $YStep + $point1->getY(); 413 414 if($DotIndex <= $dotSize) { 415 if(($boundingBoxMin == null || (($X >= $boundingBoxMin->getX()) 416 && ($Y >= $boundingBoxMin->getY()))) 417 && ($boundingBoxMax == null || (($X <= $boundingBoxMax->getX()) 418 && ($Y <= $boundingBoxMax->getY()))) 419 ) { 420 if($lineWidth == 1) 421 $this->drawAntialiasPixel( 422 new Point($X, $Y), 423 $color, $shadowProperties 424 ); 425 else { 426 $StartOffset = -($lineWidth / 2); 427 $EndOffset = ($lineWidth / 2); 428 for($j = $StartOffset; $j <= $EndOffset; $j++) { 429 $this->drawAntialiasPixel( 430 new Point($X + $j, 431 $Y + $j), 432 $color, $shadowProperties 433 ); 434 } 435 } 436 } 437 } 438 439 $DotIndex++; 440 if($DotIndex == $dotSize * 2) 441 $DotIndex = 0; 442 } 443 } 444 445 public function drawAntialiasPixel(Point $point, Color $color, ShadowProperties $shadowProperties, $alpha = 100) { 446 /* Process shadows */ 447 if($shadowProperties->active) { 448 $this->drawAntialiasPixel( 449 new Point($point->getX() + $shadowProperties->xDistance, 450 $point->getY() + $shadowProperties->yDistance), 451 $shadowProperties->color, 452 ShadowProperties::NoShadow(), 453 $shadowProperties->alpha 454 ); 455 if($shadowProperties->blur != 0) { 456 $AlphaDecay = ($shadowProperties->alpha / $shadowProperties->blur); 457 458 for($i = 1; $i <= $shadowProperties->blur; $i++) 459 $this->drawAntialiasPixel( 460 new Point($point->getX() + $shadowProperties->xDistance - $i / 2, 461 $point->getY() + $shadowProperties->yDistance - $i / 2), 462 $shadowProperties->color, 463 ShadowProperties::NoShadow(), 464 $shadowProperties->alpha - $AlphaDecay * $i 465 ); 466 for($i = 1; $i <= $shadowProperties->blur; $i++) 467 $this->drawAntialiasPixel( 468 new Point($point->getX() + $shadowProperties->xDistance + $i / 2, 469 $point->getY() + $shadowProperties->yDistance + $i / 2), 470 $shadowProperties->color, 471 ShadowProperties::NoShadow(), 472 $shadowProperties->alpha - $AlphaDecay * $i 473 ); 474 } 475 } 476 477 $Xi = floor($point->getX()); 478 $Yi = floor($point->getY()); 479 480 if($Xi == $point->getX() && $Yi == $point->getY()) { 481 if($alpha == 100) { 482 $C_Aliased = $this->allocateColor($color); 483 imagesetpixel( 484 $this->picture, 485 $point->getX(), $point->getY(), 486 $C_Aliased 487 ); 488 } else 489 $this->drawAlphaPixel($point, $alpha, $color); 490 } else { 491 $Alpha1 = (((1 - ($point->getX() - $Xi)) * (1 - ($point->getY() - $Yi)) * 100) / 100) * $alpha; 492 if($Alpha1 > $this->antialiasQuality) { 493 $this->drawAlphaPixel(new Point($Xi, $Yi), $Alpha1, $color); 494 } 495 496 $Alpha2 = ((($point->getX() - $Xi) * (1 - ($point->getY() - $Yi)) * 100) / 100) * $alpha; 497 if($Alpha2 > $this->antialiasQuality) { 498 $this->drawAlphaPixel(new Point($Xi + 1, $Yi), $Alpha2, $color); 499 } 500 501 $Alpha3 = (((1 - ($point->getX() - $Xi)) * ($point->getY() - $Yi) * 100) / 100) 502 * $alpha; 503 if($Alpha3 > $this->antialiasQuality) { 504 $this->drawAlphaPixel(new Point($Xi, $Yi + 1), $Alpha3, $color); 505 } 506 507 $Alpha4 = ((($point->getX() - $Xi) * ($point->getY() - $Yi) * 100) / 100) 508 * $alpha; 509 if($Alpha4 > $this->antialiasQuality) { 510 $this->drawAlphaPixel(new Point($Xi + 1, $Yi + 1), $Alpha4, $color); 511 } 512 } 513 } 514 515 public function drawAlphaPixel(Point $point, $alpha, Color $color) { 516 /** @todo Check that the point is within the bounds of the 517 * canvas */ 518 519 $RGB2 = imagecolorat($this->picture, $point->getX(), $point->getY()); 520 $R2 = ($RGB2 >> 16) & 0xFF; 521 $G2 = ($RGB2 >> 8) & 0xFF; 522 $B2 = $RGB2 & 0xFF; 523 524 $iAlpha = (100 - $alpha) / 100; 525 $alpha = $alpha / 100; 526 527 $Ra = floor($color->r * $alpha + $R2 * $iAlpha); 528 $Ga = floor($color->g * $alpha + $G2 * $iAlpha); 529 $Ba = floor($color->b * $alpha + $B2 * $iAlpha); 530 531 $C_Aliased = $this->allocateColor(new Color($Ra, $Ga, $Ba)); 532 imagesetpixel($this->picture, $point->getX(), $point->getY(), $C_Aliased); 533 } 534 535 /** 536 * Color helper 537 * 538 * @todo This shouldn't need to be public, it's only a temporary 539 * step while refactoring 540 */ 541 public function allocateColor(Color $color, $Factor = 0, $alpha = 100) { 542 if($Factor != 0) { 543 $color = $color->addRGBIncrement($Factor); 544 } 545 546 if($alpha == 100) { 547 return (imagecolorallocate($this->picture, $color->r, $color->g, $color->b)); 548 } else { 549 return imagecolorallocatealpha( 550 $this->picture, 551 $color->r, 552 $color->g, 553 $color->b, 554 127 * (1 - $alpha / 100) 555 ); 556 } 557 } 558 559 /** 560 * @todo This is only a temporary interface while I'm 561 * refactoring. This should eventually be removed. 562 */ 563 public function getPicture() { 564 return $this->picture; 565 } 566 567 public function getAntialiasQuality() { 568 return $this->antialiasQuality; 569 } 570 571 public function setAntialiasQuality($newQuality) { 572 if(!is_numeric($newQuality) 573 || $newQuality < 0 574 || $newQuality > 100 575 ) { 576 throw new InvalidArgumentException("Invalid argument to GDCanvas::setAntialiasQuality()"); 577 } 578 579 $this->antialiasQuality = $newQuality; 580 } 581 582 function drawText($fontSize, $angle, Point $point, Color $color, $fontName, $text, ShadowProperties $shadowProperties) { 583 if($shadowProperties->active) { 584 $gdShadowColor = $this->allocateColor($shadowProperties->color); 585 586 imagettftext( 587 $this->picture, $fontSize, $angle, 588 $point->getX() + $shadowProperties->xDistance, 589 $point->getY() + $shadowProperties->yDistance, 590 $gdShadowColor, 591 $fontName, $text 592 ); 593 } 594 595 $gdColor = $this->allocateColor($color); 596 597 imagettftext( 598 $this->picture, $fontSize, $angle, 599 $point->getX(), $point->getY(), 600 $gdColor, $fontName, $text 601 ); 602 } 603 604 function drawCircle(Point $center, $height, Color $color, ShadowProperties $shadowProperties, $width = null) { 605 if($width == null) { 606 $width = $height; 607 } 608 609 $Step = 360 / (2 * M_PI * max($width, $height)); 610 611 for($i = 0; $i <= 360; $i = $i + $Step) { 612 $X = cos($i * M_PI / 180) * $height + $center->getX(); 613 $Y = sin($i * M_PI / 180) * $width + $center->getY(); 614 $this->drawAntialiasPixel( 615 new Point($X, $Y), 616 $color, 617 $shadowProperties 618 ); 619 } 620 621 } 622 623 public function drawFilledCircle(Point $center, $height, Color $color, ShadowProperties $shadowProperties, $width = null) { 624 if($width == null) { 625 $width = $height; 626 } 627 628 $C_Circle = $this->allocateColor($color); 629 $Step = 360 / (2 * M_PI * max($width, $height)); 630 631 for($i = 90; $i <= 270; $i = $i + $Step) { 632 $X1 = cos($i * M_PI / 180) * $height + $center->getX(); 633 $Y1 = sin($i * M_PI / 180) * $width + $center->getY(); 634 $X2 = cos((180 - $i) * M_PI / 180) * $height + $center->getX(); 635 $Y2 = sin((180 - $i) * M_PI / 180) * $width + $center->getY(); 636 637 $this->drawAntialiasPixel( 638 new Point($X1 - 1, $Y1 - 1), 639 $color, 640 $shadowProperties 641 ); 642 $this->drawAntialiasPixel( 643 new Point($X2 - 1, $Y2 - 1), 644 $color, 645 $shadowProperties 646 ); 647 648 if(($Y1 - 1) > $center->getY() - max($width, $height)) { 649 imageline($this->picture, $X1, $Y1 - 1, $X2 - 1, $Y2 - 1, $C_Circle); 650 } 651 } 652 } 653 654 function drawFilledPolygon(array $points, $numPoints, Color $color, $alpha = 100) { 655 $gdColor = $this->allocateColor($color, 0, $alpha); 656 657 imagefilledpolygon( 658 $this->picture, 659 $points, 660 $numPoints, 661 $gdColor 662 ); 663 } 664 665 private $picture; 666 667 /** 668 * Quality of the antialiasing we do: 0 is maximum, 100 is minimum 669 */ 670 private $antialiasQuality; 671}