Subversion Repositories Applications.gtt

Rev

Rev 61 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
60 jpm 1
<?php
2
/*
3
 * This work is hereby released into the Public Domain.
4
 * To view a copy of the public domain dedication,
5
 * visit http://creativecommons.org/licenses/publicdomain/ or send a letter to
6
 * Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
7
 *
8
 */
9
 
10
require_once dirname(__FILE__)."/../Driver.class.php";
11
 
12
/**
13
 * Draw your objects
14
 *
15
 * @package Artichow
16
 */
17
 
18
class awGDDriver extends Driver {
19
 
20
	/**
21
	 * A GD resource
22
	 *
23
	 * @var $resource
24
	 */
25
	public $resource;
26
 
27
	public function __construct() {
28
		parent::__construct();
29
 
30
		$this->driverString = 'gd';
31
	}
32
 
33
	public function init(awImage $image) {
34
 
35
		if($this->resource === NULL) {
36
 
37
			$this->setImageSize($image->width, $image->height);
38
 
39
			// Create image
40
			$this->resource = imagecreatetruecolor($this->imageWidth, $this->imageHeight);
41
			if(!$this->resource) {
42
				awImage::drawError("Class Image: Unable to create a graph.");
43
			}
44
 
45
			imagealphablending($this->resource, TRUE);
46
 
47
			// Antialiasing is now handled by the Driver object
48
			$this->setAntiAliasing($image->getAntiAliasing());
49
 
50
			// Original color
51
			$this->filledRectangle(
52
				new awWhite,
53
				new awLine(
54
					new awPoint(0, 0),
55
					new awPoint($this->imageWidth, $this->imageHeight)
56
				)
57
			);
58
 
59
			$shadow = $image->shadow;
60
			if($shadow !== NULL) {
61
				$shadow = $shadow->getSpace();
62
				$p1 = new awPoint($shadow->left, $shadow->top);
63
				$p2 = new awPoint($this->imageWidth - $shadow->right - 1, $this->imageHeight - $shadow->bottom - 1);
64
 
65
 
66
				// Draw image background
67
				$this->filledRectangle($image->getBackground(), new awLine($p1, $p2));
68
 
69
				// Draw image border
70
				$image->border->rectangle($this, $p1, $p2);
71
			}
72
 
73
		}
74
	}
75
 
76
	public function initFromFile(awFileImage $fileImage, $file) {
77
 
78
		$image = @getimagesize((string)$file);
79
 
80
		if($image and in_array($image[2], array(2, 3))) {
81
 
82
			$fileImage->setSize($image[0], $image[1]);
83
 
84
			switch($image[2]) {
85
 
86
				case 2 :
87
					$this->resource = imagecreatefromjpeg($file);
88
					break;
89
 
90
				case 3 :
91
					$this->resource = imagecreatefrompng($file);
92
					break;
93
 
94
			}
95
 
96
			$this->setImageSize($fileImage->width, $fileImage->height);
97
		} else {
98
			awImage::drawError("Class FileImage: Artichow does not support the format of this image (must be in PNG or JPEG)");
99
		}
100
	}
101
 
102
	public function setImageSize($width, $height) {
103
 
104
		$this->imageWidth = $width;
105
		$this->imageHeight = $height;
106
 
107
	}
108
 
109
	public function setPosition($x, $y) {
110
 
111
		// Calculate absolute position
112
		$this->x = round($x * $this->imageWidth - $this->w / 2);
113
		$this->y = round($y * $this->imageHeight - $this->h / 2);
114
 
115
	}
116
 
117
	public function setAbsPosition($x, $y) {
118
 
119
		$this->x = $x;
120
		$this->y = $y;
121
 
122
	}
123
 
124
	public function movePosition($x, $y) {
125
 
126
		$this->x += (int)$x;
127
		$this->y += (int)$y;
128
 
129
	}
130
 
131
	public function setSize($w, $h) {
132
 
133
		// Calcul absolute size
134
		$this->w = round($w * $this->imageWidth);
135
		$this->h = round($h * $this->imageHeight);
136
 
137
		return $this->getSize();
138
 
139
	}
140
 
141
	public function setAbsSize($w, $h) {
142
 
143
		$this->w = $w;
144
		$this->h = $h;
145
 
146
		return $this->getSize();
147
 
148
	}
149
 
150
	public function getSize() {
151
 
152
		return array($this->w, $this->h);
153
 
154
	}
155
 
156
	public function setAntiAliasing($bool) {
157
 
158
		if(function_exists('imageantialias')) {
159
			imageantialias($this->resource, (bool)$bool);
160
 
161
			$this->antiAliasing = (bool)$bool;
162
		} else {
163
			awImage::drawErrorFile('missing-anti-aliasing');
164
		}
165
	}
166
 
167
	public function getColor(awColor $color) {
168
 
169
		if($color->alpha === 0 or function_exists('imagecolorallocatealpha') === FALSE) {
170
			$gdColor = imagecolorallocate($this->resource, $color->red, $color->green, $color->blue);
171
		} else {
172
			$gdColor = imagecolorallocatealpha($this->resource, $color->red, $color->green, $color->blue, $color->alpha);
173
		}
174
 
175
		return $gdColor;
176
	}
177
 
178
	public function copyImage(awImage $image, awPoint $p1, awPoint $p2) {
179
 
180
		list($x1, $y1) = $p1->getLocation();
181
		list($x2, $y2) = $p2->getLocation();
182
 
183
		$driver = $image->getDriver();
184
		imagecopy($this->resource, $driver->resource, $this->x + $x1, $this->y + $y1, 0, 0, $x2 - $x1, $y2 - $y1);
185
 
186
	}
187
 
188
	public function copyResizeImage(awImage $image, awPoint $d1, awPoint $d2, awPoint $s1, awPoint $s2, $resample = TRUE) {
189
 
190
		if($resample) {
191
			$function = 'imagecopyresampled';
192
		} else {
193
			$function = 'imagecopyresized';
194
		}
195
 
196
		$driver = $image->getDriver();
197
 
198
		$function(
199
			$this->resource,
200
			$driver->resource,
201
			$this->x + $d1->x, $this->y + $d1->y,
202
			$s1->x, $s1->y,
203
			$d2->x - $d1->x, $d2->y - $d1->y,
204
			$s2->x - $s1->x, $s2->y - $s1->y
205
		);
206
 
207
	}
208
 
209
	public function string(awText $text, awPoint $point, $width = NULL) {
210
 
211
		$font = $text->getFont();
212
 
213
		// Can we deal with that font?
214
		if($this->isCompatibleWithFont($font) === FALSE) {
215
			awImage::drawError('Class GDDriver: Incompatible font type (\''.get_class($font).'\')');
216
		}
217
 
218
		// Check which FontDriver to use
219
		if($font instanceof awPHPFont) {
220
			$fontDriver = $this->phpFontDriver;
221
		} else {
222
			$fontDriver = $this->fileFontDriver;
223
		}
224
 
225
		if($text->getBackground() !== NULL or $text->border->visible()) {
226
 
227
			list($left, $right, $top, $bottom) = $text->getPadding();
228
 
229
			$textWidth = $fontDriver->getTextWidth($text, $this);
230
			$textHeight = $fontDriver->getTextHeight($text, $this);
231
 
232
			$x1 = floor($point->x - $left);
233
			$y1 = floor($point->y - $top);
234
			$x2 = $x1 + $textWidth + $left + $right;
235
			$y2 = $y1 + $textHeight + $top + $bottom;
236
 
237
			$this->filledRectangle(
238
				$text->getBackground(),
239
				awLine::build($x1, $y1, $x2, $y2)
240
			);
241
 
242
			$text->border->rectangle(
243
				$this,
244
				new awPoint($x1 - 1, $y1 - 1),
245
				new awPoint($x2 + 1, $y2 + 1)
246
			);
247
 
248
		}
249
 
250
		$fontDriver->string($this, $text, $point, $width);
251
 
252
	}
253
 
254
	public function point(awColor $color, awPoint $p) {
255
 
256
		if($p->isHidden() === FALSE) {
257
			$rgb = $this->getColor($color);
258
			imagesetpixel($this->resource, $this->x + round($p->x), $this->y + round($p->y), $rgb);
259
		}
260
 
261
	}
262
 
263
	public function line(awColor $color, awLine $line) {
264
 
265
		if($line->thickness > 0 and $line->isHidden() === FALSE) {
266
 
267
			$rgb = $this->getColor($color);
268
			$thickness = $line->thickness;
269
 
270
			list($p1, $p2) = $line->getLocation();
271
 
272
			$this->startThickness($thickness);
273
 
274
			switch($line->getStyle()) {
275
 
276
				case awLine::SOLID :
277
					imageline($this->resource, $this->x + round($p1->x), $this->y + round($p1->y), $this->x + round($p2->x), $this->y + round($p2->y), $rgb);
278
					break;
279
 
280
				case awLine::DOTTED :
281
					$size = sqrt(pow($p2->y - $p1->y, 2) + pow($p2->x - $p1->x, 2));
282
					$cos = ($p2->x - $p1->x) / $size;
283
					$sin = ($p2->y - $p1->y) / $size;
284
					for($i = 0; $i <= $size; $i += 2) {
285
						$p = new awPoint(
286
							round($i * $cos + $p1->x),
287
							round($i * $sin + $p1->y)
288
						);
289
						$this->point($color, $p);
290
					}
291
					break;
292
 
293
				case awLine::DASHED :
294
					$width = $p2->x - $p1->x;
295
					$height = $p2->y - $p1->y;
296
					$size = sqrt(pow($height, 2) + pow($width, 2));
297
 
298
					if($size == 0) {
299
						return;
300
					}
301
 
302
					$cos = $width / $size;
303
					$sin = $height / $size;
304
 
305
					$functionX = ($width  > 0) ? 'min' : 'max';
306
					$functionY = ($height > 0) ? 'min' : 'max';
307
 
308
					for($i = 0; $i <= $size; $i += 6) {
309
 
310
						$t1 = new awPoint(
311
							round($i * $cos + $p1->x),
312
							round($i * $sin + $p1->y)
313
						);
314
 
315
						$t2 = new awPoint(
316
							round($functionX(($i + 3) * $cos, $width) + $p1->x),
317
							round($functionY(($i + 3) * $sin, $height) + $p1->y)
318
						);
319
 
320
						$this->line($color, new awLine($t1, $t2));
321
 
322
					}
323
					break;
324
 
325
			}
326
 
327
			$this->stopThickness($thickness);
328
 
329
		}
330
 
331
	}
332
 
333
	public function arc(awColor $color, awPoint $center, $width, $height, $from, $to) {
334
 
335
		imagefilledarc(
336
			$this->resource,
337
			$this->x + $center->x, $this->y + $center->y,
338
			$width, $height,
339
			$from, $to,
340
			$this->getColor($color),
341
			IMG_ARC_EDGED | IMG_ARC_NOFILL
342
		);
343
 
344
	}
345
 
346
	public function filledArc(awColor $color, awPoint $center, $width, $height, $from, $to) {
347
 
348
		imagefilledarc(
349
			$this->resource,
350
			$this->x + $center->x, $this->y + $center->y,
351
			$width, $height,
352
			$from, $to,
353
			$this->getColor($color),
354
			IMG_ARC_PIE
355
		);
356
 
357
	}
358
 
359
	public function ellipse(awColor $color, awPoint $center, $width, $height) {
360
 
361
		list($x, $y) = $center->getLocation();
362
 
363
		$rgb = $this->getColor($color);
364
		imageellipse(
365
			$this->resource,
366
			$this->x + $x,
367
			$this->y + $y,
368
			$width,
369
			$height,
370
			$rgb
371
		);
372
 
373
	}
374
 
375
	public function filledEllipse($background, awPoint $center, $width, $height) {
376
 
377
		if($background instanceof awColor) {
378
 
379
			list($x, $y) = $center->getLocation();
380
 
381
			$rgb = $this->getColor($background);
382
 
383
			imagefilledellipse(
384
				$this->resource,
385
				$this->x + $x,
386
				$this->y + $y,
387
				$width,
388
				$height,
389
				$rgb
390
			);
391
 
392
		} else if($background instanceof awGradient) {
393
 
394
			list($x, $y) = $center->getLocation();
395
 
396
			$x1 = $x - round($width / 2);
397
			$y1 = $y - round($height / 2);
398
			$x2 = $x1 + $width;
399
			$y2 = $y1 + $height;
400
 
401
			$gradientDriver = new awGDGradientDriver($this);
402
			$gradientDriver->filledEllipse(
403
				$background,
404
				$x1, $y1,
405
				$x2, $y2
406
			);
407
 
408
		}
409
 
410
	}
411
 
412
	public function rectangle(awColor $color, awLine $line) {
413
 
414
		list($p1, $p2) = $line->getLocation();
415
 
416
		switch($line->getStyle()) {
417
 
418
			case awLine::SOLID :
419
				$thickness = $line->getThickness();
420
				$this->startThickness($thickness);
421
				$rgb = $this->getColor($color);
422
				imagerectangle($this->resource, $this->x + $p1->x, $this->y + $p1->y, $this->x + $p2->x, $this->y + $p2->y, $rgb);
423
				$this->stopThickness($thickness);
424
				break;
425
 
426
			default :
427
 
428
				$side = clone $line;
429
 
430
 
431
 
432
				// Top side
433
				$side->setLocation(
434
					new awPoint($p1->x, $p1->y),
435
					new awPoint($p2->x, $p1->y)
436
				);
437
				$this->line($color, $side);
438
 
439
				// Right side
440
				$side->setLocation(
441
					new awPoint($p2->x, $p1->y),
442
					new awPoint($p2->x, $p2->y)
443
				);
444
				$this->line($color, $side);
445
 
446
				// Bottom side
447
				$side->setLocation(
448
					new awPoint($p1->x, $p2->y),
449
					new awPoint($p2->x, $p2->y)
450
				);
451
				$this->line($color, $side);
452
 
453
				// Left side
454
				$side->setLocation(
455
					new awPoint($p1->x, $p1->y),
456
					new awPoint($p1->x, $p2->y)
457
				);
458
				$this->line($color, $side);
459
 
460
				break;
461
 
462
		}
463
 
464
	}
465
 
466
	public function filledRectangle($background, awLine $line) {
467
 
468
		$p1 = $line->p1;
469
		$p2 = $line->p2;
470
 
471
		if($background instanceof awColor) {
472
			$rgb = $this->getColor($background);
473
			imagefilledrectangle($this->resource, $this->x + $p1->x, $this->y + $p1->y, $this->x + $p2->x, $this->y + $p2->y, $rgb);
474
		} else if($background instanceof awGradient) {
475
			$gradientDriver = new awGDGradientDriver($this);
476
			$gradientDriver->filledRectangle($background, $p1, $p2);
477
		}
478
 
479
	}
480
 
481
	public function polygon(awColor $color, awPolygon $polygon) {
482
 
483
		switch($polygon->getStyle()) {
484
 
485
			case awPolygon::SOLID :
486
				$thickness = $polygon->getThickness();
487
				$this->startThickness($thickness);
488
				$points = $this->getPolygonPoints($polygon);
489
				$rgb = $this->getColor($color);
490
				imagepolygon($this->resource, $points, $polygon->count(), $rgb);
491
				$this->stopThickness($thickness);
492
				break;
493
 
494
			default :
495
 
496
				if($polygon->count() > 1) {
497
 
498
					$prev = $polygon->get(0);
499
 
500
					$line = new awLine;
501
					$line->setStyle($polygon->getStyle());
502
					$line->setThickness($polygon->getThickness());
503
 
504
					for($i = 1; $i < $polygon->count(); $i++) {
505
						$current = $polygon->get($i);
506
						$line->setLocation($prev, $current);
507
						$this->line($color, $line);
508
						$prev = $current;
509
					}
510
 
511
					// Close the polygon
512
					$line->setLocation($prev, $polygon->get(0));
513
					$this->line($color, $line);
514
 
515
				}
516
 
517
		}
518
 
519
	}
520
 
521
	public function filledPolygon($background, awPolygon $polygon) {
522
 
523
		if($background instanceof awColor) {
524
 
525
			$points = $this->getPolygonPoints($polygon);
526
			$rgb = $this->getColor($background);
527
 
528
			imagefilledpolygon($this->resource, $points, $polygon->count(), $rgb);
529
 
530
		} else if($background instanceof awGradient) {
531
 
532
			$gradientDriver = new awGDGradientDriver($this);
533
			$gradientDriver->filledPolygon($background, $polygon);
534
 
535
		}
536
 
537
	}
538
 
539
	public function send(awImage $image) {
540
 
541
		$this->drawImage($image);
542
 
543
	}
544
 
545
	public function get(awImage $image) {
546
 
547
		return $this->drawImage($image, TRUE, FALSE);
548
 
549
	}
550
 
551
	public function getTextWidth(awText $text) {
552
		$font = $text->getFont();
553
 
554
		if($font instanceof awPHPFont) {
555
			$fontDriver = $this->phpFontDriver;
556
		} else {
557
			$fontDriver = $this->fileFontDriver;
558
		}
559
 
560
		return $fontDriver->getTextWidth($text, $this);
561
	}
562
 
563
	public function getTextHeight(awText $text) {
564
		$font = $text->getFont();
565
 
566
		if($font instanceof awPHPFont) {
567
			$fontDriver = $this->phpFontDriver;
568
		} else {
569
			$fontDriver = $this->fileFontDriver;
570
		}
571
 
572
		return $fontDriver->getTextHeight($text, $this);
573
	}
574
 
575
	protected function isCompatibleWithFont(awFont $font) {
576
		if($font instanceof awFDBFont) {
577
			return FALSE;
578
		} else {
579
			return TRUE;
580
		}
581
	}
582
 
583
	private function drawImage(awImage $image, $return = FALSE, $header = TRUE) {
584
 
585
		$format = $image->getFormatString();
586
 
587
		// Test if format is available
588
		if((imagetypes() & $image->getFormat()) === FALSE) {
589
			awImage::drawError("Class Image: Format '".$format."' is not available on your system. Check that your PHP has been compiled with the good libraries.");
590
		}
591
 
592
		// Get some infos about this image
593
		switch($format) {
594
			case 'jpeg' :
595
				$function = 'imagejpeg';
596
				break;
597
			case 'png' :
598
				$function = 'imagepng';
599
				break;
600
			case 'gif' :
601
				$function = 'imagegif';
602
				break;
603
		}
604
 
605
		// Send headers to the browser
606
		if($header === TRUE) {
607
			$image->sendHeaders();
608
		}
609
 
610
		if($return) {
611
			ob_start();
612
		}
613
 
614
		$function($this->resource);
615
 
616
		if($return) {
617
			return ob_get_clean();
618
		}
619
	}
620
 
621
	private function getPolygonPoints(awPolygon $polygon) {
622
 
623
		$points = array();
624
 
625
		foreach($polygon->all() as $point) {
626
			$points[] = $point->x + $this->x;
627
			$points[] = $point->y + $this->y;
628
		}
629
 
630
		return $points;
631
 
632
	}
633
 
634
	private function startThickness($thickness) {
635
 
636
		if($thickness > 1) {
637
 
638
			// Beurk :'(
639
			if($this->antiAliasing and function_exists('imageantialias')) {
640
				imageantialias($this->resource, FALSE);
641
			}
642
			imagesetthickness($this->resource, $thickness);
643
 
644
		}
645
 
646
	}
647
 
648
	private function stopThickness($thickness) {
649
 
650
		if($thickness > 1) {
651
 
652
			if($this->antiAliasing and function_exists('imageantialias')) {
653
				imageantialias($this->resource, TRUE);
654
			}
655
			imagesetthickness($this->resource, 1);
656
 
657
		}
658
 
659
	}
660
 
661
 
662
}
663
 
664
registerClass('GDDriver');
665
 
666
/**
667
 * To your gradients
668
 *
669
 * @package Artichow
670
 */
671
 
672
class awGDGradientDriver {
673
 
674
	/**
675
	 * A driver
676
	 *
677
	 * @var awGDDriver
678
	 */
679
	protected $driver;
680
 
681
	/**
682
	 * Build your GDGradientDriver
683
	 *
684
	 * @var awGDDriver $driver
685
	 */
686
	public function __construct(awGDDriver $driver) {
687
 
688
		$this->driver = $driver;
689
 
690
	}
691
 
692
	public function drawFilledFlatTriangle(awGradient $gradient, awPoint $a, awPoint $b, awPoint $c) {
693
 
694
		if($gradient->angle !== 0) {
695
			awImage::drawError("Class GDGradientDriver: Flat triangles can only be used with 0 degree gradients.");
696
		}
697
 
698
		// Look for right-angled triangle
699
		if($a->x !== $b->x and $b->x !== $c->x) {
700
			awImage::drawError("Class GDGradientDriver: Not right-angled flat triangles are not supported yet.");
701
		}
702
 
703
		if($a->x === $b->x) {
704
			$d = $a;
705
			$e = $c;
706
		} else {
707
			$d = $c;
708
			$e = $a;
709
		}
710
 
711
		$this->init($gradient, $b->y - $d->y);
712
 
713
		for($i = $c->y + 1; $i < $b->y; $i++) {
714
 
715
			$color = $this->color($i - $d->y);
716
			$pos = ($i - $d->y) / ($b->y - $d->y);
717
 
718
			$p1 = new awPoint($e->x, $i);
719
			$p2 = new awPoint(1 + floor($e->x - $pos * ($e->x - $d->x)), $i);
720
 
721
			$this->driver->filledRectangle($color, new awLine($p1, $p2));
722
 
723
			unset($color);
724
 
725
		}
726
 
727
	}
728
 
729
	protected function drawFilledTriangle(awGradient $gradient, awPolygon $polygon) {
730
 
731
		if($gradient->angle === 0) {
732
			$this->drawFilledTriangleVertically($gradient, $polygon);
733
		} elseif($gradient->angle === 90) {
734
			$this->drawFilledTriangleHorizontally($gradient, $polygon);
735
		}
736
 
737
	}
738
 
739
	private function drawFilledTriangleVertically(awGradient $gradient, awPolygon $polygon) {
740
		list($yMin, $yMax) = $polygon->getBoxYRange();
741
 
742
		$this->init($gradient, $yMax - $yMin);
743
 
744
		// Get the triangle line we will draw our lines from
745
		$fromLine = NULL;
746
		$lines = $polygon->getLines();
747
 
748
		$count = count($lines);
749
 
750
		// Pick the side of the triangle going from the top
751
		// to the bottom of the surrounding box
752
		for($i = 0; $i < $count; $i++) {
753
			if($lines[$i]->isTopToBottom($polygon)) {
754
				list($fromLine) = array_splice($lines, $i, 1);
755
				break;
756
			}
757
		}
758
 
759
		// If for some reason the three points are aligned,
760
		// $fromLine will still be NULL
761
		if($fromLine === NULL) {
762
			return;
763
		}
764
 
765
		$fillLine = NULL;
766
		for($y = round($yMin); $y < round($yMax); $y++) {
767
 
768
			$fromX = $fromLine->getXFrom($y);
769
 
770
			$toX = array();
771
			foreach($lines as $line) {
772
				$xValue = $line->getXFrom($y);
773
 
774
				if(!is_null($xValue)) {
775
					$toX[] = $xValue;
776
				}
777
			}
778
 
779
			if(count($toX) === 1) {
780
				$fillLine = new Line(
781
					new Point($fromX, $y),
782
					new Point($toX[0], $y)
783
				);
784
			} else {
785
 
786
				$line1 = new Line(
787
					new Point($fromX, $y),
788
					new Point($toX[0], $y)
789
				);
790
				$line2 = new  Line(
791
					new Point($fromX, $y),
792
					new Point($toX[1], $y)
793
				);
794
 
795
				if($line1->getSize() < $line2->getSize()) {
796
					$fillLine = $line1;
797
				} else {
798
					$fillLine = $line2;
799
				}
800
			}
801
 
802
			if(!$fillLine->isPoint()) {
803
				$color = $this->color($y - $yMin);
804
				$this->driver->line($color, $fillLine);
805
 
806
				unset($color);
807
			}
808
		}
809
 
810
	}
811
 
812
	private function drawFilledTriangleHorizontally(awGradient $gradient, awPolygon $polygon) {
813
		list($xMin, $xMax) = $polygon->getBoxXRange();
814
 
815
		$this->init($gradient, $xMax - $xMin);
816
 
817
		// Get the triangle line we will draw our lines from
818
		$fromLine = NULL;
819
		$lines = $polygon->getLines();
820
 
821
		$count = count($lines);
822
 
823
		// Pick the side of the triangle going all the way
824
		// from the left side to the right side of the surrounding box
825
		for($i = 0; $i < $count; $i++) {
826
			if($lines[$i]->isLeftToRight($polygon)) {
827
				list($fromLine) = array_splice($lines, $i, 1);
828
				break;
829
			}
830
		}
831
 
832
		// If for some reason the three points are aligned,
833
		// $fromLine will still be NULL
834
		if($fromLine === NULL) {
835
			return;
836
		}
837
 
838
		$fillLine = NULL;
839
		for($x = round($xMin); $x < round($xMax); $x++) {
840
 
841
			$fromY = floor($fromLine->getYFrom($x));
842
 
843
			$toY = array();
844
			foreach($lines as $line) {
845
				$yValue = $line->getYFrom($x);
846
 
847
				if(!is_null($yValue)) {
848
					$toY[] = floor($yValue);
849
				}
850
			}
851
 
852
			if(count($toY) === 1) {
853
				$fillLine = new Line(
854
					new Point($x, $fromY),
855
					new Point($x, $toY[0])
856
				);
857
			} else {
858
 
859
				$line1 = new Line(
860
					new Point($x, $fromY),
861
					new Point($x, $toY[0])
862
				);
863
				$line2 = new  Line(
864
					new Point($x, $fromY),
865
					new Point($x, $toY[1])
866
				);
867
 
868
				if($line1->getSize() < $line2->getSize()) {
869
					$fillLine = $line1;
870
				} else {
871
					$fillLine = $line2;
872
				}
873
			}
874
 
875
			$color = $this->color($x - $xMin);
876
			if($fillLine->isPoint()) {
877
				$this->driver->point($color, $fillLine->p1);
878
			} elseif($fillLine->getSize() >= 1) {
879
				$this->driver->line($color, $fillLine);
880
			}
881
			unset($color);
882
		}
883
 
884
	}
885
 
886
	public function filledRectangle(awGradient $gradient, awPoint $p1, awPoint $p2) {
887
 
888
		list($x1, $y1) = $p1->getLocation();
889
		list($x2, $y2) = $p2->getLocation();
890
 
891
		if($y1 < $y2) {
892
			$y1 ^= $y2 ^= $y1 ^= $y2;
893
		}
894
 
895
		if($x2 < $x1) {
896
			$x1 ^= $x2 ^= $x1 ^= $x2;
897
		}
898
 
899
		if($gradient instanceof awLinearGradient) {
900
			$this->rectangleLinearGradient($gradient, new awPoint($x1, $y1), new awPoint($x2, $y2));
901
		} else {
902
			awImage::drawError("Class GDGradientDriver: This gradient is not supported by rectangles.");
903
		}
904
 
905
	}
906
 
907
	public function filledPolygon(awGradient $gradient, awPolygon $polygon) {
908
 
909
		if($gradient instanceof awLinearGradient) {
910
			$this->polygonLinearGradient($gradient, $polygon);
911
		} else {
912
			awImage::drawError("Class GDGradientDriver: This gradient is not supported by polygons.");
913
		}
914
 
915
	}
916
 
917
	protected function rectangleLinearGradient(awLinearGradient $gradient, awPoint $p1, awPoint $p2) {
918
 
919
		list($x1, $y1) = $p1->getLocation();
920
		list($x2, $y2) = $p2->getLocation();
921
 
922
		if($y1 - $y2 > 0) {
923
 
924
			if($gradient->angle === 0) {
925
 
926
				$this->init($gradient, $y1 - $y2);
927
 
928
				for($i = $y2; $i <= $y1; $i++) {
929
 
930
					$color = $this->color($i - $y2);
931
 
932
					$p1 = new awPoint($x1, $i);
933
					$p2 = new awPoint($x2, $i);
934
 
935
					$this->driver->filledRectangle($color, new awLine($p1, $p2));
936
 
937
					unset($color);
938
 
939
				}
940
 
941
			} else if($gradient->angle === 90) {
942
 
943
				$this->init($gradient, $x2 - $x1);
944
 
945
				for($i = $x1; $i <= $x2; $i++) {
946
 
947
					$color = $this->color($i - $x1);
948
 
949
					$p1 = new awPoint($i, $y2);
950
					$p2 = new awPoint($i, $y1);
951
 
952
					$this->driver->filledRectangle($color, new awLine($p1, $p2));
953
 
954
					unset($color);
955
 
956
				}
957
 
958
			}
959
 
960
		}
961
 
962
	}
963
 
964
	public function filledEllipse(awGradient $gradient, $x1, $y1, $x2, $y2) {
965
 
966
		if($y1 < $y2) {
967
			$y1 ^= $y2 ^= $y1 ^= $y2;
968
		}
969
 
970
		if($x2 < $x1) {
971
			$x1 ^= $x2 ^= $x1 ^= $x2;
972
		}
973
 
974
		if($gradient instanceof awRadialGradient) {
975
			$this->ellipseRadialGradient($gradient, $x1, $y1, $x2, $y2);
976
		} else if($gradient instanceof awLinearGradient) {
977
			$this->ellipseLinearGradient($gradient, $x1, $y1, $x2, $y2);
978
		} else {
979
			awImage::drawError("Class GDGradientDriver: This gradient is not supported by ellipses.");
980
		}
981
 
982
	}
983
 
984
	protected function ellipseRadialGradient(awGradient $gradient, $x1, $y1, $x2, $y2) {
985
 
986
		if($y1 - $y2 > 0) {
987
 
988
			if($y1 - $y2 != $x2 - $x1) {
989
				awImage::drawError("Class GDGradientDriver: Radial gradients are only implemented on circle, not ellipses.");
990
			}
991
 
992
			$c = new awPoint($x1 + ($x2 - $x1) / 2, $y1 + ($y2 - $y1) / 2);
993
			$r = ($x2 - $x1) / 2;
994
			$ok = array();
995
 
996
			// Init gradient
997
			$this->init($gradient, $r);
998
 
999
			for($i = 0; $i <= $r; $i += 0.45) {
1000
 
1001
				$p = ceil((2 * M_PI * $i));
1002
 
1003
				if($p > 0) {
1004
					$interval = 360 / $p;
1005
				} else {
1006
					$interval = 360;
1007
				}
1008
 
1009
				$color = $this->color($i);
1010
 
1011
				for($j = 0; $j < 360; $j += $interval) {
1012
 
1013
					$rad = ($j / 360) * (2 * M_PI);
1014
 
1015
					$x = round($i * cos($rad));
1016
					$y = round($i * sin($rad));
1017
 
1018
					$l = sqrt($x * $x + $y * $y);
1019
 
1020
					if($l <= $r) {
1021
 
1022
						if(
1023
							array_key_exists((int)$x, $ok) === FALSE or
1024
							array_key_exists((int)$y, $ok[$x]) === FALSE
1025
						) {
1026
 
1027
							// Print the point
1028
							$this->driver->point($color, new awPoint($c->x + $x, $c->y + $y));
1029
 
1030
							$ok[(int)$x][(int)$y] = TRUE;
1031
 
1032
						}
1033
 
1034
					}
1035
 
1036
				}
1037
 
1038
				unset($color);
1039
 
1040
			}
1041
 
1042
		}
1043
 
1044
	}
1045
 
1046
	protected function ellipseLinearGradient(awGradient $gradient, $x1, $y1, $x2, $y2) {
1047
 
1048
		// Gauche->droite : 90°
1049
 
1050
		if($y1 - $y2 > 0) {
1051
 
1052
			if($y1 - $y2 != $x2 - $x1) {
1053
				awImage::drawError("Class GDGradientDriver: Linear gradients are only implemented on circle, not ellipses.");
1054
			}
1055
 
1056
			$r = ($x2 - $x1) / 2;
1057
 
1058
			// Init gradient
1059
			$this->init($gradient, $x2 - $x1);
1060
 
1061
			for($i = -$r; $i <= $r; $i++) {
1062
 
1063
				$h = sin(acos($i / $r)) * $r;
1064
 
1065
				$color = $this->color($i + $r);
1066
 
1067
				if($gradient->angle === 90) {
1068
 
1069
					// Print the line
1070
					$p1 = new awPoint(
1071
						$x1 + $i + $r,
1072
						round(max($y2 + $r - $h + 1, $y2))
1073
					);
1074
 
1075
					$p2 = new awPoint(
1076
						$x1 + $i + $r,
1077
						round(min($y1 - $r + $h - 1, $y1))
1078
					);
1079
 
1080
				} else {
1081
 
1082
					// Print the line
1083
					$p1 = new awPoint(
1084
						round(max($x1 + $r - $h + 1, $x1)),
1085
						$y2 + $i + $r
1086
					);
1087
 
1088
					$p2 = new awPoint(
1089
						round(min($x2 - $r + $h - 1, $x2)),
1090
						$y2 + $i + $r
1091
					);
1092
 
1093
				}
1094
 
1095
				$this->driver->filledRectangle($color, new awLine($p1, $p2));
1096
 
1097
				unset($color);
1098
 
1099
			}
1100
 
1101
		}
1102
 
1103
	}
1104
 
1105
	protected function polygonLinearGradient(awLinearGradient $gradient, awPolygon $polygon) {
1106
 
1107
		$count = $polygon->count();
1108
 
1109
		if($count >= 4) {
1110
 
1111
			$left = $polygon->get(0);
1112
			$right = $polygon->get($count - 1);
1113
 
1114
			if($gradient->angle === 0) {
1115
 
1116
				// Get polygon maximum and minimum
1117
				$offset = $polygon->get(0);
1118
				$max = $min = $offset->y;
1119
				for($i = 1; $i < $count - 1; $i++) {
1120
					$offset = $polygon->get($i);
1121
					$max = max($max, $offset->y);
1122
					$min = min($min, $offset->y);
1123
				}
1124
 
1125
				$this->init($gradient, $max - $min);
1126
 
1127
				$prev = $polygon->get(1);
1128
 
1129
				$sum = 0;
1130
 
1131
				for($i = 2; $i < $count - 1; $i++) {
1132
 
1133
					$current = $polygon->get($i);
1134
 
1135
					$interval = 1;
1136
 
1137
					if($i !== $count - 2) {
1138
						$current->x -= $interval;
1139
					}
1140
 
1141
					if($current->x - $prev->x > 0) {
1142
 
1143
						// Draw rectangle
1144
						$x1 = $prev->x;
1145
						$x2 = $current->x;
1146
						$y1 = max($prev->y, $current->y);
1147
						$y2 = $left->y;
1148
 
1149
						$gradient = new awLinearGradient(
1150
							$this->color($max - $min - ($y2 - $y1)),
1151
							$this->color($max - $min),
1152
 
1153
						);
1154
 
1155
						if($y1 > $y2) {
1156
							$y2 = $y1;
1157
						}
1158
 
1159
						$this->driver->filledRectangle(
1160
							$gradient,
1161
							awLine::build($x1, $y1, $x2, $y2)
1162
						);
1163
 
1164
						$top = ($prev->y < $current->y) ? $current : $prev;
1165
						$bottom = ($prev->y >= $current->y) ? $current : $prev;
1166
 
1167
						$gradient = new awLinearGradient(
1168
							$this->color($bottom->y - $min),
1169
							$this->color($max - $min - ($y2 - $y1)),
1170
 
1171
						);
1172
 
1173
 
1174
						$gradientDriver = new awGDGradientDriver($this->driver);
1175
						$gradientDriver->drawFilledFlatTriangle(
1176
							$gradient,
1177
							new awPoint($prev->x, min($prev->y, $current->y)),
1178
							$top,
1179
							new awPoint($current->x, min($prev->y, $current->y))
1180
						);
1181
						unset($gradientDriver);
1182
 
1183
						$sum += $current->x - $prev->x;
1184
 
1185
					}
1186
 
1187
					$prev = $current;
1188
					$prev->x += $interval;
1189
 
1190
				}
1191
 
1192
			} else if($gradient->angle === 90) {
1193
 
1194
				$width = $right->x - $left->x;
1195
				$this->init($gradient, $width);
1196
 
1197
				$pos = 1;
1198
				$next = $polygon->get($pos++);
1199
 
1200
				$this->next($polygon, $pos, $prev, $next);
1201
 
1202
				for($i = 0; $i <= $width; $i++) {
1203
 
1204
					$x = $left->x + $i;
1205
 
1206
					$y1 = round($prev->y + ($next->y - $prev->y) * (($i + $left->x - $prev->x) / ($next->x - $prev->x)));
1207
					$y2 = $left->y;
1208
 
1209
					// Draw line
1210
					$color = $this->color($i);
1211
					// YaPB : PHP does not handle alpha on lines
1212
					$this->driver->filledRectangle($color, awLine::build($x, $y1, $x, $y2));
1213
 
1214
					unset($color);
1215
 
1216
					// Jump to next point
1217
					if($next->x == $i + $left->x) {
1218
 
1219
						$this->next($polygon, $pos, $prev, $next);
1220
 
1221
					}
1222
 
1223
				}
1224
 
1225
			}
1226
 
1227
		} else if($count === 3) {
1228
			$this->drawFilledTriangle(
1229
				$gradient,
1230
				$polygon
1231
			);
1232
		}
1233
 
1234
	}
1235
 
1236
	private function next($polygon, &$pos, &$prev, &$next) {
1237
 
1238
		do {
1239
			$prev = $next;
1240
			$next = $polygon->get($pos++);
1241
		}
1242
		while($next->x - $prev->x == 0 and $pos < $polygon->count());
1243
 
1244
	}
1245
 
1246
	/**
1247
	 * Start colors
1248
	 *
1249
	 * @var int
1250
	 */
1251
	private $r1, $g1, $b1, $a1;
1252
 
1253
	/**
1254
	 * Stop colors
1255
	 *
1256
	 * @var int
1257
	 */
1258
	private $r2, $g2, $b2, $a2;
1259
 
1260
	/**
1261
	 * Gradient size in pixels
1262
	 *
1263
	 * @var int
1264
	 */
1265
	private $size;
1266
 
1267
 
1268
	private function init(awGradient $gradient, $size) {
1269
 
1270
		list(
1271
			$this->r1, $this->g1, $this->b1, $this->a1
1272
		) = $gradient->from->rgba();
1273
 
1274
		list(
1275
			$this->r2, $this->g2, $this->b2, $this->a2
1276
		) = $gradient->to->rgba();
1277
 
1278
		$this->size = $size;
1279
	}
1280
 
1281
	private function color($pos) {
1282
 
1283
		return new awColor(
1284
			$this->getRed($pos),
1285
			$this->getGreen($pos),
1286
			$this->getBlue($pos),
1287
			$this->getAlpha($pos)
1288
		);
1289
 
1290
	}
1291
 
1292
 
1293
	private function getRed($pos) {
1294
		if((float)$this->size !== 0.0) {
1295
			return (int)round($this->r1 + ($pos / $this->size) * ($this->r2 - $this->r1));
1296
		} else {
1297
			return 0;
1298
		}
1299
	}
1300
 
1301
	private function getGreen($pos) {
1302
		if((float)$this->size !== 0.0) {
1303
			return (int)round($this->g1 + ($pos / $this->size) * ($this->g2 - $this->g1));
1304
		} else {
1305
			return 0;
1306
		}
1307
	}
1308
 
1309
	private function getBlue($pos) {
1310
		if((float)$this->size !== 0.0) {
1311
			return (int)round($this->b1 + ($pos / $this->size) * ($this->b2 - $this->b1));
1312
		} else {
1313
			return 0;
1314
		}
1315
	}
1316
 
1317
	private function getAlpha($pos) {
1318
		if((float)$this->size !== 0.0) {
1319
			return (int)round(($this->a1 + ($pos / $this->size) * ($this->a2 - $this->a1)) / 127 * 100);
1320
		} else {
1321
			return 0;
1322
		}
1323
	}
1324
 
1325
}
1326
 
1327
registerClass('GDGradientDriver');
1328
 
1329
/*
1330
 * Check for GD2
1331
 */
1332
if(function_exists('imagecreatetruecolor') === FALSE) {
1333
	awImage::drawErrorFile('missing-gd2');
1334
}
1335
 
1336
?>