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