1<?php
2/*
3** Zabbix
4** Copyright (C) 2001-2021 Zabbix SIA
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19**/
20
21
22class CPieGraphDraw extends CGraphDraw {
23
24	const DEFAULT_HEADER_PADDING_TOP = 30;
25
26	const GRAPH_WIDTH_MIN = 20;
27	const GRAPH_HEIGHT_MIN = 20;
28
29	public function __construct($type = GRAPH_TYPE_PIE) {
30		parent::__construct($type);
31		$this->background = false;
32		$this->sum = false;
33		$this->exploderad = 1;
34		$this->exploderad3d = 3;
35		$this->graphheight3d = 12;
36		$this->shiftlegendright = 17 * 7 + 7 + 10; // count of static chars * px/char + for color rectangle + space
37	}
38
39	/********************************************************************************************************/
40	/* PRE CONFIG: ADD / SET / APPLY
41	/********************************************************************************************************/
42	public function addItem($itemid, $calc_fnc = CALC_FNC_AVG, $color = null, $type = null) {
43		$items = CMacrosResolverHelper::resolveItemNames([get_item_by_itemid($itemid)]);
44
45		$this->items[$this->num] = reset($items);
46
47		$host = get_host_by_hostid($this->items[$this->num]['hostid']);
48
49		$this->items[$this->num]['host'] = $host['host'];
50		$this->items[$this->num]['hostname'] = $host['name'];
51		$this->items[$this->num]['color'] = is_null($color) ? 'Dark Green' : $color;
52		$this->items[$this->num]['calc_fnc'] = is_null($calc_fnc) ? CALC_FNC_AVG : $calc_fnc;
53		$this->items[$this->num]['calc_type'] = is_null($type) ? GRAPH_ITEM_SIMPLE : $type;
54
55		$this->num++;
56	}
57
58	public function switchPie3D($type = false) {
59		if ($type) {
60			$this->type = $type;
61		}
62		else {
63			switch ($this->type) {
64				case GRAPH_TYPE_EXPLODED:
65					$this->type = GRAPH_TYPE_3D_EXPLODED;
66					break;
67				case GRAPH_TYPE_3D_EXPLODED:
68					$this->type = GRAPH_TYPE_EXPLODED;
69					break;
70				case GRAPH_TYPE_3D:
71					$this->type = GRAPH_TYPE_PIE;
72					break;
73				case GRAPH_TYPE_PIE:
74					$this->type = GRAPH_TYPE_3D;
75					break;
76				default:
77					$this->type = GRAPH_TYPE_PIE;
78			}
79		}
80		return $this->type;
81	}
82
83	protected function calc3dheight($height) {
84		$this->graphheight3d = (int) ($height / 20);
85	}
86
87	protected function calcExplodedCenter($anglestart, $angleend, $x, $y, $count) {
88		$count *= $this->exploderad;
89		$anglemid = (int) (($anglestart + $angleend) / 2);
90
91		$y+= round($count * sin(deg2rad($anglemid)));
92		$x+= round($count * cos(deg2rad($anglemid)));
93
94		return [$x, $y];
95	}
96
97	protected function calcExplodedRadius($sizeX, $sizeY, $count) {
98		$count *= $this->exploderad * 2;
99		$sizeX -= $count;
100		$sizeY -= $count;
101		return [$sizeX, $sizeY];
102	}
103
104	protected function calc3DAngle($sizeX, $sizeY) {
105		$sizeY *= GRAPH_3D_ANGLE / 90;
106		return [$sizeX, round($sizeY)];
107	}
108
109	protected function selectData() {
110		$this->data = [];
111		$now = time();
112
113		if (isset($this->stime)) {
114			$this->from_time = $this->stime;
115			$this->to_time = $this->stime + $this->period;
116		}
117		else {
118			$this->to_time = $now;
119			$this->from_time = $this->to_time - $this->period;
120		}
121
122		$strvaluelength = 0; // we need to know how long in px will be our legend
123
124		// fetch values for items with the "last" function
125		$lastValueItems = [];
126		foreach ($this->items as $item) {
127			if ($item['calc_fnc'] == CALC_FNC_LST) {
128				$lastValueItems[] = $item;
129			}
130		}
131		if ($lastValueItems) {
132			$history = Manager::History()->getLastValues($lastValueItems);
133		}
134
135		$config = select_config();
136		$items = [];
137
138		for ($i = 0; $i < $this->num; $i++) {
139			$item = get_item_by_itemid($this->items[$i]['itemid']);
140			$from_time = $this->from_time;
141			$to_time = $this->to_time;
142
143			$to_resolve = [];
144
145			// Override item history setting with housekeeping settings, if they are enabled in config.
146			if ($config['hk_history_global']) {
147				$item['history'] = timeUnitToSeconds($config['hk_history']);
148			}
149			else {
150				$to_resolve[] = 'history';
151			}
152
153			if ($config['hk_trends_global']) {
154				$item['trends'] = timeUnitToSeconds($config['hk_trends']);
155			}
156			else {
157				$to_resolve[] = 'trends';
158			}
159
160			// Otherwise, resolve user macro and parse the string. If successful, convert to seconds.
161			if ($to_resolve) {
162				$item = CMacrosResolverHelper::resolveTimeUnitMacros([$item], $to_resolve)[0];
163
164				$simple_interval_parser = new CSimpleIntervalParser();
165
166				if (!$config['hk_history_global']) {
167					if ($simple_interval_parser->parse($item['history']) != CParser::PARSE_SUCCESS) {
168						show_error_message(_s('Incorrect value for field "%1$s": %2$s.', 'history',
169							_('invalid history storage period')
170						));
171						exit;
172					}
173					$item['history'] = timeUnitToSeconds($item['history']);
174				}
175
176				if (!$config['hk_trends_global']) {
177					if ($simple_interval_parser->parse($item['trends']) != CParser::PARSE_SUCCESS) {
178						show_error_message(_s('Incorrect value for field "%1$s": %2$s.', 'trends',
179							_('invalid trend storage period')
180						));
181						exit;
182					}
183					$item['trends'] = timeUnitToSeconds($item['trends']);
184				}
185			}
186
187			$this->data[$this->items[$i]['itemid']]['last'] = isset($history[$item['itemid']])
188				? $history[$item['itemid']][0]['value'] : null;
189			$this->data[$this->items[$i]['itemid']]['shift_min'] = 0;
190			$this->data[$this->items[$i]['itemid']]['shift_max'] = 0;
191			$this->data[$this->items[$i]['itemid']]['shift_avg'] = 0;
192
193			$item['source'] = ($item['trends'] == 0 || ($item['history'] > time() - ($from_time + $this->period / 2)))
194				? 'history'
195				: 'trends';
196			$items[] = $item;
197		}
198
199		$results = Manager::History()->getGraphAggregationByWidth($items, $from_time, $to_time);
200		$i = 0;
201
202		foreach ($items as $item) {
203			if (array_key_exists($item['itemid'], $results)) {
204				$result = $results[$item['itemid']];
205				$this->dataFrom = $result['source'];
206
207				foreach ($result['data'] as $row) {
208					$this->data[$item['itemid']]['min'] = $row['min'];
209					$this->data[$item['itemid']]['max'] = $row['max'];
210					$this->data[$item['itemid']]['avg'] = $row['avg'];
211					$this->data[$item['itemid']]['clock'] = $row['clock'];
212				}
213				unset($result);
214			}
215			else {
216				$this->dataFrom = $item['source'];
217			}
218
219			switch ($this->items[$i]['calc_fnc']) {
220				case CALC_FNC_MIN:
221					$fncName = 'min';
222					break;
223				case CALC_FNC_MAX:
224					$fncName = 'max';
225					break;
226				case CALC_FNC_LST:
227					$fncName = 'last';
228					break;
229				case CALC_FNC_AVG:
230				default:
231					$fncName = 'avg';
232			}
233
234			$item_value = empty($this->data[$item['itemid']][$fncName])
235				? 0
236				: abs($this->data[$item['itemid']][$fncName]);
237
238			if ($this->items[$i]['calc_type'] == GRAPH_ITEM_SUM) {
239				$this->background = $i;
240				$graph_sum = $item_value;
241			}
242
243			$this->sum += $item_value;
244
245			$convertedUnit = strlen(convertUnits([
246				'value' => $item_value,
247				'units' => $item['units']
248			]));
249			$strvaluelength = max($strvaluelength, $convertedUnit);
250			$i++;
251		}
252
253		if (isset($graph_sum)) {
254			$this->sum = $graph_sum;
255		}
256		$this->shiftlegendright += $strvaluelength * 7;
257	}
258
259	protected function drawLegend() {
260		$shiftY = $this->shiftY + $this->shiftYLegend;
261		$fontSize = 8;
262
263		// check if host name will be displayed
264		$displayHostName = (count(array_unique(zbx_objectValues($this->items, 'hostname'))) > 1);
265
266		// calculate function name X shift
267		$functionNameXShift = 0;
268
269		foreach ($this->items as $item) {
270			$name = $displayHostName ? $item['hostname'].': '.$item['name_expanded'] : $item['name_expanded'];
271			$dims = imageTextSize($fontSize, 0, $name);
272
273			if ($dims['width'] > $functionNameXShift) {
274				$functionNameXShift = $dims['width'];
275			}
276		}
277
278		// display items
279		$i = 0;
280		$top_padding = $this->with_vertical_padding ? 10 : -(static::DEFAULT_TOP_BOTTOM_PADDING / 2);
281
282		foreach ($this->items as $item) {
283			$color = $this->getColor($item['color'], 0);
284
285			// function name
286			switch ($item['calc_fnc']) {
287				case CALC_FNC_MIN:
288					$fncName = 'min';
289					$fncRealName = _('min');
290					break;
291				case CALC_FNC_MAX:
292					$fncName = 'max';
293					$fncRealName = _('max');
294					break;
295				case CALC_FNC_LST:
296					$fncName = 'last';
297					$fncRealName = _('last');
298					break;
299				case CALC_FNC_AVG:
300				default:
301					$fncName = 'avg';
302					$fncRealName = _('avg');
303			}
304
305			if (isset($this->data[$item['itemid']])
306					&& isset($this->data[$item['itemid']][$fncName])) {
307				$dataValue = $this->data[$item['itemid']][$fncName];
308				$proc = ($this->sum == 0) ? 0 : ($dataValue * 100) / $this->sum;
309
310				$strValue = sprintf(_('Value').': %s ('.(round($proc) != round($proc, 2) ? '%0.2f' : '%0.0f').'%%)',
311					convertUnits([
312						'value' => $dataValue,
313						'units' => $this->items[$i]['units']
314					]),
315					$proc
316				);
317
318				$str = '['.$fncRealName.']';
319			}
320			else {
321				$strValue = _('Value: no data');
322
323				$str = '['._('no data').']';
324			}
325
326			// item name
327			imageText(
328				$this->im,
329				$fontSize,
330				0,
331				$this->shiftXleft + 15,
332				$this->sizeY + $shiftY + 14 * $i + 5,
333				$this->getColor($this->graphtheme['textcolor'], 0),
334				$displayHostName ? $item['hostname'].': '.$item['name_expanded'] : $item['name_expanded']
335			);
336
337			// function name
338			imageText(
339				$this->im,
340				$fontSize,
341				0,
342				$this->shiftXleft + $functionNameXShift + 30,
343				$this->sizeY + $shiftY + 14 * $i + 5,
344				$this->getColor($this->graphtheme['textcolor'], 0),
345				$str
346			);
347
348			// left square fill
349			imagefilledrectangle(
350				$this->im,
351				$this->shiftXleft,
352				$this->sizeY + $shiftY + 14 * $i - 5,
353				$this->shiftXleft + 10,
354				$this->sizeY + $shiftY + 5 + 14 * $i,
355				$color
356			);
357
358			// left square frame
359			imagerectangle(
360				$this->im,
361				$this->shiftXleft,
362				$this->sizeY + $shiftY + 14 * $i - 5,
363				$this->shiftXleft + 10,
364				$this->sizeY + $shiftY + 5 + 14 * $i,
365				$this->getColor('Black No Alpha')
366			);
367
368			$shiftX = $this->fullSizeX - $this->shiftlegendright - $this->shiftXright + 25;
369
370			// right square fill
371			imagefilledrectangle(
372				$this->im,
373				$shiftX - 10,
374				$this->shiftY + $top_padding + 14 * $i,
375				$shiftX,
376				$this->shiftY + $top_padding + 10 + 14 * $i,
377				$color
378			);
379
380			// right square frame
381			imagerectangle(
382				$this->im,
383				$shiftX - 10,
384				$this->shiftY + $top_padding + 14 * $i,
385				$shiftX,
386				$this->shiftY + $top_padding + 10 + 14 * $i,
387				$this->GetColor('Black No Alpha')
388			);
389
390			// item value
391			imagetext(
392				$this->im,
393				$fontSize,
394				0,
395				$shiftX + 5,
396				$this->shiftY + $top_padding + 14 * $i + 10,
397				$this->getColor($this->graphtheme['textcolor'], 0),
398				$strValue
399			);
400
401			$i++;
402		}
403
404		if ($this->sizeY < 120) {
405			return;
406		}
407	}
408
409	protected function drawElementPie($values) {
410		$sum = $this->sum;
411
412		if ($this->background !== false) {
413			$least = 0;
414			foreach ($values as $item => $value) {
415				if ($item != $this->background) {
416					$least += $value;
417				}
418			}
419			$values[$this->background] -= $least;
420		}
421
422		if ($sum <= 0) {
423			$values = [0 => 1];
424			$sum = 1;
425			$isEmptyData = true;
426		}
427		else {
428			$isEmptyData = false;
429		}
430
431		$sizeX = $this->sizeX;
432		$sizeY = $this->sizeY;
433
434		if ($this->type == GRAPH_TYPE_EXPLODED) {
435			list($sizeX, $sizeY) = $this->calcExplodedRadius($sizeX, $sizeY, count($values));
436		}
437
438		$xc = $x = (int) $this->sizeX / 2 + $this->shiftXleft;
439		$yc = $y = (int) $this->sizeY / 2 + $this->shiftY;
440
441		$anglestart = 0;
442		$angleend = 0;
443
444		foreach ($values as $item => $value) {
445			if ($value == 0) {
446				continue;
447			}
448
449			$angleend += (int) (360 * $value / $sum) + 1;
450			$angleend = ($angleend > 360) ? 360 : $angleend;
451
452			if (($angleend - $anglestart) < 1) {
453				continue;
454			}
455
456			if ($this->type == GRAPH_TYPE_EXPLODED) {
457				list($x, $y) = $this->calcExplodedCenter($anglestart, $angleend, $xc, $yc, count($values));
458			}
459
460			imagefilledarc(
461				$this->im,
462				$x,
463				$y,
464				$sizeX,
465				$sizeY,
466				$anglestart,
467				$angleend,
468				$this->getColor((!$isEmptyData ? $this->items[$item]['color'] : 'FFFFFF'), 0),
469				IMG_ARC_PIE
470			);
471			imagefilledarc(
472				$this->im,
473				$x,
474				$y,
475				$sizeX,
476				$sizeY,
477				$anglestart,
478				$angleend,
479				$this->getColor('Black'),
480				IMG_ARC_PIE | IMG_ARC_EDGED | IMG_ARC_NOFILL
481			);
482			$anglestart = $angleend;
483		}
484	}
485
486	protected function drawElementPie3D($values) {
487		$sum = $this->sum;
488
489		if ($this->background !== false) {
490			$least = 0;
491			foreach ($values as $item => $value) {
492				if ($item != $this->background) {
493					$least += $value;
494				}
495			}
496			$values[$this->background] -= $least;
497		}
498
499		if ($sum <= 0) {
500			$values = [0 => 1];
501			$sum = 1;
502			$isEmptyData = true;
503		}
504		else {
505			$isEmptyData = false;
506		}
507
508		$sizeX = $this->sizeX;
509		$sizeY = $this->sizeY;
510
511		$this->exploderad = $this->exploderad3d;
512
513		if ($this->type == GRAPH_TYPE_3D_EXPLODED) {
514			list($sizeX, $sizeY) = $this->calcExplodedRadius($sizeX, $sizeY, count($values));
515		}
516
517		list($sizeX, $sizeY) = $this->calc3DAngle($sizeX, $sizeY);
518
519		$xc = $x = (int) $this->sizeX / 2 + $this->shiftXleft;
520		$yc = $y = (int) $this->sizeY / 2 + $this->shiftY;
521
522		// bottom angle line
523		$anglestart = 0;
524		$angleend = 0;
525
526		foreach ($values as $item => $value) {
527			if ($value == 0) {
528				continue;
529			}
530
531			$angleend += (int) (360 * $value / $sum) + 1;
532			$angleend = ($angleend > 360) ? 360 : $angleend;
533
534			if (($angleend - $anglestart) < 1) {
535				continue;
536			}
537
538			if ($this->type == GRAPH_TYPE_3D_EXPLODED) {
539				list($x, $y) = $this->calcExplodedCenter($anglestart, $angleend, $xc, $yc, count($values));
540			}
541
542			imagefilledarc(
543				$this->im,
544				$x,
545				$y + $this->graphheight3d + 1,
546				$sizeX,
547				$sizeY,
548				$anglestart,
549				$angleend,
550				$this->getShadow((!$isEmptyData ? $this->items[$item]['color'] : 'FFFFFF'), 0),
551				IMG_ARC_PIE
552			);
553			imagefilledarc(
554				$this->im,
555				$x,
556				$y + $this->graphheight3d + 1,
557				$sizeX,
558				$sizeY,
559				$anglestart,
560				$angleend,
561				$this->getColor('Black'),
562				IMG_ARC_PIE | IMG_ARC_EDGED | IMG_ARC_NOFILL
563			);
564			$anglestart = $angleend;
565		}
566
567		// 3d effect
568		for ($i = $this->graphheight3d; $i > 0; $i--) {
569			$anglestart = 0;
570			$angleend = 0;
571
572			foreach ($values as $item => $value) {
573				if ($value == 0) {
574					continue;
575				}
576
577				$angleend += (int) (360 * $value / $sum) + 1;
578				$angleend = ($angleend > 360) ? 360 : $angleend;
579
580				if (($angleend - $anglestart) < 1) {
581					continue;
582				}
583				elseif ($this->sum == 0) {
584					continue;
585				}
586
587				if ($this->type == GRAPH_TYPE_3D_EXPLODED) {
588					list($x, $y) = $this->calcExplodedCenter($anglestart, $angleend, $xc, $yc, count($values));
589				}
590
591				imagefilledarc(
592					$this->im,
593					$x,
594					$y + $i,
595					$sizeX,
596					$sizeY,
597					$anglestart,
598					$angleend,
599					$this->getShadow((!$isEmptyData ? $this->items[$item]['color'] : 'FFFFFF'), 0),
600					IMG_ARC_PIE
601				);
602				$anglestart = $angleend;
603			}
604		}
605
606		$anglestart = 0;
607		$angleend = 0;
608
609		foreach ($values as $item => $value) {
610			if ($value == 0) {
611				continue;
612			}
613
614			$angleend += (int) (360 * $value / $sum) + 1;
615			$angleend = ($angleend > 360) ? 360 : $angleend;
616
617			if (($angleend - $anglestart) < 1) {
618				continue;
619			}
620
621			if ($this->type == GRAPH_TYPE_3D_EXPLODED) {
622				list($x, $y) = $this->calcExplodedCenter($anglestart, $angleend, $xc, $yc, count($values));
623			}
624
625			imagefilledarc(
626				$this->im,
627				$x,
628				$y,
629				$sizeX,
630				$sizeY,
631				$anglestart,
632				$angleend,
633				$this->getColor((!$isEmptyData ? $this->items[$item]['color'] : 'FFFFFF'), 0),
634				IMG_ARC_PIE
635			);
636			imagefilledarc(
637				$this->im,
638				$x,
639				$y,
640				$sizeX,
641				$sizeY,
642				$anglestart,
643				$angleend,
644				$this->getColor('Black'),
645				IMG_ARC_PIE | IMG_ARC_EDGED | IMG_ARC_NOFILL
646			);
647			$anglestart = $angleend;
648		}
649	}
650
651	public function draw() {
652		$debug_mode = CWebUser::getDebugMode();
653		if ($debug_mode) {
654			$start_time = microtime(true);
655		}
656		set_image_header();
657		$this->calculateTopPadding();
658
659		$this->selectData();
660		if (hasErrorMesssages()) {
661			show_messages();
662		}
663
664		$this->shiftYLegend = 20;
665		$this->shiftXleft = 10;
666		$this->shiftXright = 0;
667		$this->fullSizeX = $this->sizeX;
668		$this->fullSizeY = $this->sizeY;
669
670		if ($this->sizeX < 300 || $this->sizeY < 200) {
671			$this->showLegend(0);
672		}
673
674		if ($this->drawLegend == 1) {
675			$this->sizeX -= $this->shiftXleft + $this->shiftXright + $this->shiftlegendright;
676			$this->sizeY -= $this->shiftY + $this->shiftYLegend + 14 * $this->num + 8;
677		}
678		elseif ($this->with_vertical_padding) {
679			$this->sizeX -= $this->shiftXleft * 2;
680			$this->sizeY -= $this->shiftY * 2;
681		}
682
683		if (!$this->with_vertical_padding) {
684			if ($this->drawLegend == 1) {
685				// Increase size of graph by sum of: 8px legend font size and 5px legend item bottom shift.
686				$this->sizeY += 13;
687			}
688			else {
689				// Remove y shift if only graph is rendered (no labels, header, vertical paddings).
690				$this->shiftY = 0;
691			}
692		}
693
694		$this->sizeX = min($this->sizeX, $this->sizeY);
695		$this->sizeY = min($this->sizeX, $this->sizeY);
696
697		if ($this->sizeX + $this->shiftXleft > $this->fullSizeX) {
698			$this->sizeX = $this->fullSizeX - $this->shiftXleft - $this->shiftXleft;
699			$this->sizeY = min($this->sizeX, $this->sizeY);
700		}
701
702		$this->calc3dheight($this->sizeY);
703
704		$this->exploderad = (int) $this->sizeX / 100;
705		$this->exploderad3d = (int) $this->sizeX / 60;
706
707		if (function_exists('ImageColorExactAlpha') && function_exists('ImageCreateTrueColor') && @imagecreatetruecolor(1, 1)) {
708			$this->im = imagecreatetruecolor($this->fullSizeX, $this->fullSizeY);
709		}
710		else {
711			$this->im = imagecreate($this->fullSizeX, $this->fullSizeY);
712		}
713		$this->initColors();
714		$this->drawRectangle();
715		$this->drawHeader();
716
717		// for each metric
718		$values = [];
719		for ($i = 0; $i < $this->num; $i++) {
720			$data = &$this->data[$this->items[$i]['itemid']];
721
722			if (!isset($data)) {
723				continue;
724			}
725
726			switch ($this->items[$i]['calc_fnc']) {
727				case CALC_FNC_MIN:
728					$fncName = 'min';
729					break;
730				case CALC_FNC_MAX:
731					$fncName = 'max';
732					break;
733				case CALC_FNC_LST:
734					$fncName = 'last';
735					break;
736				case CALC_FNC_AVG:
737				default:
738					$fncName = 'avg';
739			}
740
741			$values[$i] = empty($this->data[$this->items[$i]['itemid']][$fncName])
742				? 0
743				: abs($this->data[$this->items[$i]['itemid']][$fncName]);
744		}
745
746		switch ($this->type) {
747			case GRAPH_TYPE_EXPLODED:
748				$this->drawElementPie($values);
749				break;
750			case GRAPH_TYPE_3D:
751				$this->drawElementPie3D($values);
752				break;
753			case GRAPH_TYPE_3D_EXPLODED:
754				$this->drawElementPie3D($values);
755				break;
756			default:
757				$this->drawElementPie($values);
758		}
759
760		if ($this->drawLegend == 1) {
761			$this->drawLegend();
762		}
763
764		if ($debug_mode) {
765			$str = sprintf('%0.2f', microtime(true) - $start_time);
766			imageText(
767				$this->im,
768				6,
769				90,
770				$this->fullSizeX - 2,
771				$this->fullSizeY - 5,
772				$this->getColor('Gray'),
773				_s('Data from %1$s. Generated in %2$s sec.', $this->dataFrom, $str)
774			);
775		}
776
777		unset($this->items, $this->data);
778
779		imageOut($this->im);
780	}
781}
782