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;
49 $this->data['branches'][count($this->data['branches'])] = array(
50 "id" => $this->brach_id++,
51 "uid" => $this->branch_uid++,
60 public function parse($commits) {
61 if(!GitConfig::GITGRAPH_ENABLE)
63 $brach_id = $this->brach_id;
64 $branch_uid = $this->branch_id;
65 $first_commit = (count($this->data['branches']) == 0 ? true : false);
66 foreach($commits as $commit) {
68 $commit['merge'] = array();
69 $commit['dot_type'] = self::DOT_TYPE_NORMAL;
71 $first_commit = false;
72 $this->data['branches'][0] = array();
73 $branch = &$this->data['branches'][0];
74 $branch['id'] = $brach_id++;
75 $branch['uid'] = $branch_uid++;
76 $branch['active'] = true;
79 foreach($this->data['branches'] as $id => &$cbranch) {
80 if($cbranch['next'] == $commit['id']) {
81 if($first && !$cbranch['pre_merge']) {
82 $branch = &$this->data['branches'][$id];
87 foreach($this->data['branches'] as $id => &$cbranch) {
88 if($cbranch['next'] == $commit['id']) {
90 $branch = &$this->data['branches'][$id];
92 } else if($cbranch['id'] == $branch['id']) {
94 $commit['merge'][] = array("point" => $cbranch['id'], "start" => true, "end" => false);
95 $cbranch['active'] = false;
96 if($cbranch['pre_merge']) {
97 $cbranch['pre_merge_start'] = true;
98 $cbranch['pre_merge_id'] = $branch['id'];
99 $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1];
106 $this->data['branches'][count($this->data['branches'])] = array();
107 $branch = &$this->data['branches'][count($this->data['branches'])-1];
108 $branch['id'] = $brach_id++;
109 $branch['uid'] = $branch_uid++;
110 $branch['active'] = true;
111 $branch['pre_merge'] = false;
116 if(array_key_exists('parent', $commit) && count($commit['parent']) > 1) {
118 for($j = 1; $j < count($commit['parent']); $j++) {
120 foreach($this->data['branches'] as $cbranch) {
121 if(array_key_exists('next', $cbranch) && $cbranch['next'] == $commit['parent'][$j]) {
128 foreach($this->data['branches'] as $bid => &$cbranch) {
129 if(!$cbranch['active']) {
135 $this->data['branches'][count($this->data['branches'])] = array();
136 $cbranch = &$this->data['branches'][count($this->data['branches'])-1];
137 $cbranch['id'] = $brach_id++;
139 $cbranch['uid'] = $branch_uid++;
140 $cbranch['active'] = true;
141 $cbranch['pre_merge'] = true;
142 $cbranch['next'] = $commit['parent'][$j];
144 $commit['merge'][] = array("point" => $cbranch['id'], "start" => false, "end" => $add);
145 $commit['dot_type'] = self::DOT_TYPE_MERGE;
146 $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1];
149 } else if(!array_key_exists('parent', $commit) || count($commit['parent']) == 0) {
150 $branch['active'] = false;
151 $commit['dot_type'] = self::DOT_TYPE_INIT;
153 $branch['next'] = ((array_key_exists('parent', $commit) && count($commit['parent'])) ? $commit['parent'][0] : null);
154 $branch['pre_merge'] = false;
155 $this->data['ubranches'][$branch['uid']] = $this->data['branches'][$branch['id']-1];
157 $commit['dot'] = $branch['id'];
159 foreach($this->data['branches'] as $id => $cbranch) {
160 $commit['branches'][$id] = $cbranch;
163 $this->graph[$commit['id']] = $this->graph_data($commit);
164 //echo$commit['id']." ".$this->get_graph($commit['id'])."\n";
168 private function graph_data($commit) {
170 $data['d'] = array();
171 $data['d']['p'] = $commit['dot']; //dot position
172 $data['d']['type'] = 'a';
173 $data['l'] = array(); //lines
174 $data['br'] = array(); //branches for color check
175 foreach($commit['branches'] as $branch) {
176 if($branch['pre_merge'] || $commit['merge']) {
177 $data['br'][] = $branch['uid'];
179 if($branch['active']) {
180 if($commit['dot'] == $branch['id']) continue;
182 if($commit['merge']) {
183 foreach($commit['merge'] as $merge) {
184 if($merge['point'] == $branch['id']) {
191 if($branch['id'] > $this->max_branches) continue;
192 $data['l'][] = $branch['id'];
195 $data['m'] = array(); //merges
196 if($commit['merge']) {
197 foreach($commit['merge'] as $merge) {
198 $mergepoint = array();
199 $mergepoint['hl'] = array();
200 $mergepoint['p'] = $merge['point'];
202 if($commit['dot'] <= $this->max_branches)
203 $mergepoint['dd'] = ($commit['dot'] < $merge['point'] ? 'r' : 'l');
205 $mergepoint['dd'] = 'n';
207 $mergepoint['ml'] = ($merge['start'] ? 1 : 0) + ($merge['end'] ? 2 : 0);
208 if($merge['point'] <= $this->max_branches)
209 $mergepoint['md']=($commit['dot'] < $merge['point'] ? 'l' : 'r');
211 $mergepoint['md'] = 'n';
212 $min = ($commit['dot'] < $merge['point'] ? $commit['dot'] : $merge['point']) + 1;
213 $max = ($commit['dot'] < $merge['point'] ? $merge['point'] : $commit['dot']);
214 for($i = $min; $i < $max; $i++) {
215 if($i > $this->max_branches) continue;
216 $mergepoint['hl'][] = $i;
218 $data['m'][] = $mergepoint;
221 $data['d']['type'] = $commit['dot_type'];
225 public function get_graph($id) {
226 if(!GitConfig::GITGRAPH_ENABLE)
228 $graph = $this->graph[$id];
229 $data = $graph['d']['p'].$graph['d']['type'].count($this->data['branches']).'('.implode(',', $graph['l']).')';
231 foreach($graph['m'] as $merge) {
234 $first_merge = false;
235 $data.=$merge['p'].$merge['dd'].$merge['md'].$merge['ml'];
236 foreach($merge['hl'] as $hline)
239 $graph['cs'] = array();
240 foreach($graph['br'] as $buid) {
242 $branch = $this->data['ubranches'][$buid];
243 if($branch['pre_merge'] && array_key_exists('pre_merge_start', $branch) && $branch['pre_merge_start'])
244 $graph['cs'][] = $branch['id']."=".$branch['pre_merge_id'];
246 if(count($graph['cs'])) {
247 $data .= '('.implode(',', $graph['cs']).')';
249 if(GitConfig::GITGRAPH_BASE64)
250 $data = base64_encode($data);
254 public function get_header_graph() {
258 foreach($this->data['branches'] as $branch) {
259 if(array_key_exists('sticky', $branch) && $branch['sticky']) {
260 $name = explode('/', $branch['name'][0], 3);
261 $dataadd .= "//".$branch['id'].":".$name[2];
265 $data.=$branchcount.$dataadd;
266 if(GitConfig::GITGRAPH_BASE64)
267 $data = base64_encode($data);
272 class graph_image_generator {
273 private $max_branches;
277 private $header_height = false;
278 private $colors, $color_swap = array();
280 public function __construct() {
281 $this->max_branches = GitConfig::GITGRAPH_MAX_BRANCHES;
282 $this->size = GitConfig::GITGRAPH_END_SIZE;
283 $this->tile_size = GitConfig::GITGRAPH_TILE_SIZE;
285 $this->colors = array(
288 array(array(0, 255, 0), array(0, 192, 0)),
290 array(128, 128, 128),
297 public function generate($data) {
298 if(!GitConfig::GITGRAPH_ENABLE)
300 $data = $this->parse_data($data);
302 header('Content-Type: text/plain');
303 die(base64_decode("ICAgIC0tLS0tLS0tOi0tLS0tLS0tDQogICAgICAgICAgLC0iLC5fX19fX19fIC8NCiAgICAgICAgIC8gKSB8ICAgLC0tLS0nXA0KICAgICAgICAgXC9fX3wuLSINCiAgICAgICAgLl8vL19cXF8NCk5vdCB3aGF0IHlvdSBleHBlY3RlZCwgZWVlaD8="));
306 $count = $data['count'];
307 if($count > $this->max_branches)
308 $count = $this->max_branches;
309 $this->image = imagecreatetruecolor($count * $this->size, $this->size);
310 $transparentIndex = imagecolorallocate($this->image, 0xFF, 0xFF, 0xFF);
311 imagefill($this->image, 0, 0, $transparentIndex);
313 $this->apply_data($data);
315 imagecolortransparent($this->image, $transparentIndex);
317 header('Content-Type: image/png');
318 imagepng($this->image);
319 imagedestroy($this->image);
322 private function display_header($header) {
323 $header = explode("//",$header);
325 $header = array_slice($header, 1);
326 if($count > $this->max_branches)
327 $count = $this->max_branches;
328 if(!$this->header_height) {
330 foreach($header as $head) {
331 $head = explode(":", $head, 2);
333 if(strlen($name) > $maxlen)
334 $maxlen = strlen($name);
336 $this->header_height = $maxlen * 2 + 15;
338 $image = imagecreatetruecolor($count * $this->size + 60, $this->header_height);
339 $transparentIndex = imagecolorallocate($image, 217, 216, 209);
340 imagefill($image, 0, 0, $transparentIndex);
342 foreach($header as $head) {
343 $head = explode(":", $head, 2);
344 $color = $this->get_color($head[0], true);
347 $color = imagecolorallocatealpha($image, $color[0], $color[1], $color[2], 0);
348 imagettftext($image, 8, 28, ($head[0]-1) * $this->size + 10, $this->header_height-2, $color, realpath(dirname(__FILE__)."/../")."/res/arial.ttf", $name);
350 if(!$branches) die();
351 imagecolortransparent($image, $transparentIndex);
352 header('Content-Type: image/png');
354 imagedestroy($image);
357 private function parse_data($data) {
358 if(GitConfig::GITGRAPH_BASE64)
359 $data = base64_decode($data);
360 if(!preg_match("/^([0-9]+)([abc]{1})([0-9]+)\(([^\)]*)\)([a-z0-9,\|]*)(\(([^\)]*)\)|)/i", $data, $matches)) {
361 if(preg_match("/head:(.*)/i", $data, $matches)) {
362 $this->display_header(substr($data, strlen("head:")));
368 $data['dot'] = array();
369 $data['dot']['pos'] = $matches[1];
370 $data['dot']['type'] = $matches[2];
371 $data['count'] = $matches[3];
373 if($matches[4] != '')
374 $data['l'] = explode(',', $matches[4]);
376 $data['l'] = array();
378 $data['m'] = array();
379 if($matches[5] != '') {
380 foreach(explode('|', $matches[5]) as $m) {
383 if(!preg_match("/^([0-9]+)([rln]{1})([rln]{1})([0123]{1})(.*)/i", $m, $sm))
385 $merge['hl'] = array();
386 $merge['pos'] = $sm[1];
387 $merge['dd'] = $sm[2];
388 $merge['md'] = $sm[3];
389 $merge['ml'] = $sm[4];
391 $merge['hl'] = explode(',', $sm[5]);
393 $data['m'][] = $merge;
396 if($matches[6] != '' && $matches[7] != '') {
397 foreach(explode(',', $matches[7]) as $cswap) {
398 $cswap = explode("=", $cswap);
399 $this->color_swap[$cswap[0]] = $cswap[1];
405 function image_set_color($src, $color) {
406 imagesavealpha($src, true);
407 imagealphablending($src, false);
409 for ($x = 0; $x < $this->size; $x++) {
410 for ($y = 0; $y < $this->size; $y++) {
411 $src_pix = imagecolorat($src,$x,$y);
412 $src_pix_array = imagecolorsforindex($src, $src_pix);
414 imagesetpixel($src, $x, $y, imagecolorallocatealpha($src, $color[0], $color[1], $color[2], $src_pix_array['alpha']));
419 function overlay_image($name, $left, $color = false) {
420 $image2 = imagecreatefrompng($name);
423 $this->image_set_color($image2, $color);
425 imagecopyresampled($this->image, $image2, $left, 0, 0, 0, $this->size, $this->size, $this->tile_size, $this->tile_size);
428 function get_color($id, $text = false) {
429 if(array_key_exists($id, $this->color_swap))
430 $id = $this->color_swap[$id];
431 $color_array = $this->colors[($id - 1) % count($this->colors)];
432 if($text && is_array($color_array[0]) && $color_array[1])
433 return $color_array[1];
434 return (is_array($color_array[0]) ? $color_array[0] : $color_array);
437 private function apply_data($data) {
438 foreach($data['l'] as $l)
439 $this->overlay_image("res/line.png", ($l-1) * $this->size, $this->get_color($l));
440 foreach($data['m'] as $m) {
442 $this->overlay_image("res/dot_merge_right.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos']));
443 else if($m['dd'] == 'l')
444 $this->overlay_image("res/dot_merge_left.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos']));
446 $this->overlay_image("res/".(($m['ml'] & 1) ? "branch" : "merge")."_right.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
447 else if($m['md'] == 'l')
448 $this->overlay_image("res/".(($m['ml'] & 1) ? "branch" : "merge")."_left.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
450 $this->overlay_image("res/line.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
451 foreach($m['hl'] as $hl) {
452 $this->overlay_image("res/line_h.png", ($hl - 1) * $this->size, $this->get_color($m['pos']));
455 if($data['dot']['type'] == 'a')
456 $this->overlay_image("res/dot.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
457 else if($data['dot']['type'] == 'b')
458 $this->overlay_image("res/dot_merge.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
459 else if($data['dot']['type'] == 'c')
460 $this->overlay_image("res/dot_init.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));