2 /* graph.class.php - phpgitweb
3 * Copyright (C) 2011-2012 Philipp Kreil (pk910)
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 class graph_data_generator {
20 const DOT_TYPE_NORMAL = 'a';
21 const DOT_TYPE_MERGE = 'b';
22 const DOT_TYPE_INIT = 'c';
24 private $data = array();
25 private $graph = array();
26 private $max_branches, $brach_id = 1, $branch_id = 1;
28 public function __construct() {
29 $this->max_branches = GitConfig::GITGRAPH_MAX_BRANCHES;
30 $this->data['branches'] = array();
31 $this->data['ubranches'] = array();
34 public function add_branch($first_id, $name) {
36 foreach($this->data['branches'] as &$branch) {
37 if($branch['next'] == $first_id) {
39 $branch['name'][] = $name;
46 $this->data['branches'][count($this->data['branches'])] = array(
47 "id" => $this->brach_id++,
48 "uid" => $this->branch_uid++,
51 "name" => array($name),
57 public function parse($commits) {
58 if(!GitConfig::GITGRAPH_ENABLE)
60 $brach_id = $this->brach_id;
61 $branch_uid = $this->branch_id;
62 $first_commit = (count($this->data['branches']) == 0 ? true : false);
63 foreach($commits as $commit) {
65 $commit['merge'] = array();
66 $commit['dot_type'] = self::DOT_TYPE_NORMAL;
68 $first_commit = false;
69 $this->data['branches'][0] = array();
70 $branch = &$this->data['branches'][0];
71 $branch['id'] = $brach_id++;
72 $branch['uid'] = $branch_uid++;
73 $branch['active'] = true;
76 foreach($this->data['branches'] as $id => &$cbranch) {
77 if($cbranch['next'] == $commit['id']) {
78 if($first && !$cbranch['pre_merge']) {
79 $branch = &$this->data['branches'][$id];
84 foreach($this->data['branches'] as $id => &$cbranch) {
85 if($cbranch['next'] == $commit['id']) {
87 $branch = &$this->data['branches'][$id];
89 } else if($cbranch['id'] == $branch['id']) {
91 $commit['merge'][] = array("point" => $cbranch['id'], "start" => true, "end" => false);
92 $cbranch['active'] = false;
93 if($cbranch['pre_merge']) {
94 $cbranch['pre_merge_start'] = true;
95 $cbranch['pre_merge_id'] = $branch['id'];
96 $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1];
103 $this->data['branches'][count($this->data['branches'])] = array();
104 $branch = &$this->data['branches'][count($this->data['branches'])-1];
105 $branch['id'] = $brach_id++;
106 $branch['uid'] = $branch_uid++;
107 $branch['active'] = true;
108 $branch['pre_merge'] = false;
113 if(array_key_exists('parent', $commit) && count($commit['parent']) > 1) {
115 for($j = 1; $j < count($commit['parent']); $j++) {
117 foreach($this->data['branches'] as $cbranch) {
118 if(array_key_exists('next', $cbranch) && $cbranch['next'] == $commit['parent'][$j]) {
125 foreach($this->data['branches'] as $bid => &$cbranch) {
126 if(!$cbranch['active']) {
132 $this->data['branches'][count($this->data['branches'])] = array();
133 $cbranch = &$this->data['branches'][count($this->data['branches'])-1];
134 $cbranch['id'] = $brach_id++;
136 $cbranch['uid'] = $branch_uid++;
137 $cbranch['active'] = true;
138 $cbranch['pre_merge'] = true;
139 $cbranch['next'] = $commit['parent'][$j];
141 $commit['merge'][] = array("point" => $cbranch['id'], "start" => false, "end" => $add);
142 $commit['dot_type'] = self::DOT_TYPE_MERGE;
143 $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1];
146 } else if(!array_key_exists('parent', $commit) || count($commit['parent']) == 0) {
147 $branch['active'] = false;
148 $commit['dot_type'] = self::DOT_TYPE_INIT;
150 $branch['next'] = (array_key_exists('parent', $commit) ? $commit['parent'][0] : null);
151 $branch['pre_merge'] = false;
152 $this->data['ubranches'][$branch['uid']] = $this->data['branches'][$branch['id']-1];
154 $commit['dot'] = $branch['id'];
156 foreach($this->data['branches'] as $id => $cbranch) {
157 $commit['branches'][$id] = $cbranch;
160 $this->graph[$commit['id']] = $this->graph_data($commit);
161 //echo$commit['id']." ".$this->get_graph($commit['id'])."\n";
165 private function graph_data($commit) {
167 $data['d'] = array();
168 $data['d']['p'] = $commit['dot']; //dot position
169 $data['d']['type'] = 'a';
170 $data['l'] = array(); //lines
171 $data['br'] = array(); //branches for color check
172 foreach($commit['branches'] as $branch) {
173 if($branch['pre_merge'] || $commit['merge']) {
174 $data['br'][] = $branch['uid'];
176 if($branch['active']) {
177 if($commit['dot'] == $branch['id']) continue;
179 if($commit['merge']) {
180 foreach($commit['merge'] as $merge) {
181 if($merge['point'] == $branch['id']) {
188 if($branch['id'] > $this->max_branches) continue;
189 $data['l'][] = $branch['id'];
192 $data['m'] = array(); //merges
193 if($commit['merge']) {
194 foreach($commit['merge'] as $merge) {
195 $mergepoint = array();
196 $mergepoint['hl'] = array();
197 $mergepoint['p'] = $merge['point'];
199 if($commit['dot'] <= $this->max_branches)
200 $mergepoint['dd'] = ($commit['dot'] < $merge['point'] ? 'r' : 'l');
202 $mergepoint['dd'] = 'n';
204 $mergepoint['ml'] = ($merge['start'] ? 1 : 0) + ($merge['end'] ? 2 : 0);
205 if($merge['point'] <= $this->max_branches)
206 $mergepoint['md']=($commit['dot'] < $merge['point'] ? 'l' : 'r');
208 $mergepoint['md'] = 'n';
209 $min = ($commit['dot'] < $merge['point'] ? $commit['dot'] : $merge['point']) + 1;
210 $max = ($commit['dot'] < $merge['point'] ? $merge['point'] : $commit['dot']);
211 for($i = $min; $i < $max; $i++) {
212 if($i > $this->max_branches) continue;
213 $mergepoint['hl'][] = $i;
215 $data['m'][] = $mergepoint;
218 $data['d']['type'] = $commit['dot_type'];
222 public function get_graph($id) {
223 if(!GitConfig::GITGRAPH_ENABLE)
225 $graph = $this->graph[$id];
226 $data = $graph['d']['p'].$graph['d']['type'].count($this->data['branches']).'('.implode(',', $graph['l']).')';
228 foreach($graph['m'] as $merge) {
231 $first_merge = false;
232 $data.=$merge['p'].$merge['dd'].$merge['md'].$merge['ml'];
233 foreach($merge['hl'] as $hline)
236 $graph['cs'] = array();
237 foreach($graph['br'] as $buid) {
239 $branch = $this->data['ubranches'][$buid];
240 if($branch['pre_merge'] && array_key_exists('pre_merge_start', $branch) && $branch['pre_merge_start'])
241 $graph['cs'][] = $branch['id']."=".$branch['pre_merge_id'];
243 if(count($graph['cs'])) {
244 $data .= '('.implode(',', $graph['cs']).')';
246 if(GitConfig::GITGRAPH_BASE64)
247 $data = base64_encode($data);
251 public function get_header_graph() {
255 foreach($this->data['branches'] as $branch) {
256 if(array_key_exists('sticky', $branch) && $branch['sticky']) {
257 $name = explode('/', $branch['name'][0], 3);
258 $dataadd .= "\n".$branch['id'].":".$name[2];
262 $data.=$branchcount.$dataadd;
263 if(GitConfig::GITGRAPH_BASE64)
264 $data = base64_encode($data);
269 class graph_image_generator {
270 private $max_branches;
274 private $header_height = false;
275 private $colors, $color_swap = array();
277 public function __construct() {
278 $this->max_branches = GitConfig::GITGRAPH_MAX_BRANCHES;
279 $this->size = GitConfig::GITGRAPH_END_SIZE;
280 $this->tile_size = GitConfig::GITGRAPH_TILE_SIZE;
282 $this->colors = array(
285 array(array(0, 255, 0), array(0, 192, 0)),
287 array(128, 128, 128),
294 public function generate($data) {
295 if(!GitConfig::GITGRAPH_ENABLE)
297 $data = $this->parse_data($data);
301 $count = $data['count'];
302 if($count > $this->max_branches)
303 $count = $this->max_branches;
304 $this->image = imagecreatetruecolor($count * $this->size, $this->size);
305 $transparentIndex = imagecolorallocate($this->image, 0xFF, 0xFF, 0xFF);
306 imagefill($this->image, 0, 0, $transparentIndex);
308 $this->apply_data($data);
310 imagecolortransparent($this->image, $transparentIndex);
312 header('Content-Type: image/png');
313 imagepng($this->image);
314 imagedestroy($this->image);
317 private function display_header($header) {
318 $header = explode("\n",$header);
320 $header = array_slice($header, 1);
321 if($count > $this->max_branches)
322 $count = $this->max_branches;
323 if(!$this->header_height) {
325 foreach($header as $head) {
326 $head = explode(":", $head, 2);
328 if(strlen($name) > $maxlen)
329 $maxlen = strlen($name);
331 $this->header_height = $maxlen * 2 + 15;
333 $image = imagecreatetruecolor($count * $this->size + 60, $this->header_height);
334 $transparentIndex = imagecolorallocate($image, 217, 216, 209);
335 imagefill($image, 0, 0, $transparentIndex);
337 foreach($header as $head) {
338 $head = explode(":", $head, 2);
339 $color = $this->get_color($head[0], true);
342 $color = imagecolorallocatealpha($image, $color[0], $color[1], $color[2], 0);
343 imagettftext($image, 8, 28, ($head[0]-1) * $this->size + 10, $this->header_height-2, $color, realpath(dirname(__FILE__)."/../")."/res/arial.ttf", $name);
345 if(!$branches) die();
346 imagecolortransparent($image, $transparentIndex);
347 header('Content-Type: image/png');
349 imagedestroy($image);
352 private function parse_data($data) {
353 if(GitConfig::GITGRAPH_BASE64)
354 $data = base64_decode($data);
355 if(!preg_match("/^([0-9]+)([abc]{1})([0-9]+)\(([^\)]*)\)([a-z0-9,\|]*)(\(([^\)]*)\)|)/i", $data, $matches)) {
356 if(preg_match("/head:(.*)/i", $data, $matches)) {
357 $this->display_header(substr($data, strlen("head:")));
363 $data['dot'] = array();
364 $data['dot']['pos'] = $matches[1];
365 $data['dot']['type'] = $matches[2];
366 $data['count'] = $matches[3];
368 if($matches[4] != '')
369 $data['l'] = explode(',', $matches[4]);
371 $data['l'] = array();
373 $data['m'] = array();
374 if($matches[5] != '') {
375 foreach(explode('|', $matches[5]) as $m) {
378 if(!preg_match("/^([0-9]+)([rln]{1})([rln]{1})([0123]{1})(.*)/i", $m, $sm))
380 $merge['hl'] = array();
381 $merge['pos'] = $sm[1];
382 $merge['dd'] = $sm[2];
383 $merge['md'] = $sm[3];
384 $merge['ml'] = $sm[4];
386 $merge['hl'] = explode(',', $sm[5]);
388 $data['m'][] = $merge;
391 if($matches[6] != '' && $matches[7] != '') {
392 foreach(explode(',', $matches[7]) as $cswap) {
393 $cswap = explode("=", $cswap);
394 $this->color_swap[$cswap[0]] = $cswap[1];
400 function image_set_color($src, $color) {
401 imagesavealpha($src, true);
402 imagealphablending($src, false);
404 for ($x = 0; $x < $this->size; $x++) {
405 for ($y = 0; $y < $this->size; $y++) {
406 $src_pix = imagecolorat($src,$x,$y);
407 $src_pix_array = imagecolorsforindex($src, $src_pix);
409 imagesetpixel($src, $x, $y, imagecolorallocatealpha($src, $color[0], $color[1], $color[2], $src_pix_array['alpha']));
414 function overlay_image($name, $left, $color = false) {
415 $image2 = imagecreatefrompng($name);
418 $this->image_set_color($image2, $color);
420 imagecopyresampled($this->image, $image2, $left, 0, 0, 0, $this->size, $this->size, $this->tile_size, $this->tile_size);
423 function get_color($id, $text = false) {
424 if(array_key_exists($id, $this->color_swap))
425 $id = $this->color_swap[$id];
426 $color_array = $this->colors[($id - 1) % count($this->colors)];
427 if($text && is_array($color_array[0]) && $color_array[1])
428 return $color_array[1];
429 return (is_array($color_array[0]) ? $color_array[0] : $color_array);
432 private function apply_data($data) {
433 foreach($data['l'] as $l)
434 $this->overlay_image("res/line.png", ($l-1) * $this->size, $this->get_color($l));
435 foreach($data['m'] as $m) {
437 $this->overlay_image("res/dot_merge_right.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos']));
438 else if($m['dd'] == 'l')
439 $this->overlay_image("res/dot_merge_left.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos']));
441 $this->overlay_image("res/".(($m['ml'] & 1) ? "branch" : "merge")."_right.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
442 else if($m['md'] == 'l')
443 $this->overlay_image("res/".(($m['ml'] & 1) ? "branch" : "merge")."_left.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
445 $this->overlay_image("res/line.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
446 foreach($m['hl'] as $hl) {
447 $this->overlay_image("res/line_h.png", ($hl - 1) * $this->size, $this->get_color($m['pos']));
450 if($data['dot']['type'] == 'a')
451 $this->overlay_image("res/dot.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
452 else if($data['dot']['type'] == 'b')
453 $this->overlay_image("res/dot_merge.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
454 else if($data['dot']['type'] == 'c')
455 $this->overlay_image("res/dot_init.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));