. */ class graph_data_generator { const DOT_TYPE_NORMAL = 'a'; const DOT_TYPE_MERGE = 'b'; const DOT_TYPE_INIT = 'c'; private $data = array(); private $graph = array(); private $max_branches, $brach_id = 1, $branch_id = 1; public function __construct() { $this->max_branches = GitConfig::GITGRAPH_MAX_BRANCHES; $this->data['branches'] = array(); $this->data['ubranches'] = array(); } public function add_branch($first_id, $name) { $existing = false; foreach($this->data['branches'] as &$branch) { if($branch['next'] == $first_id) { $existing = true; $branch['name'][] = $name; break; } } unset($branch); if($existing) continue; $this->data['branches'][count($this->data['branches'])] = array( "id" => $this->brach_id++, "uid" => $this->branch_uid++, "active" => true, "sticky" => true, "name" => array($name), "next" => $first_id, "pre_merge" => false ); } public function parse($commits) { if(!GitConfig::GITGRAPH_ENABLE) return; $brach_id = $this->brach_id; $branch_uid = $this->branch_id; $first_commit = (count($this->data['branches']) == 0 ? true : false); foreach($commits as $commit) { //get current branch $commit['merge'] = array(); $commit['dot_type'] = self::DOT_TYPE_NORMAL; if($first_commit) { $first_commit = false; $this->data['branches'][0] = array(); $branch = &$this->data['branches'][0]; $branch['id'] = $brach_id++; $branch['uid'] = $branch_uid++; $branch['active'] = true; } else { $first = true; foreach($this->data['branches'] as $id => &$cbranch) { if($cbranch['next'] == $commit['id']) { if($first && !$cbranch['pre_merge']) { $branch = &$this->data['branches'][$id]; $first = false; } } } foreach($this->data['branches'] as $id => &$cbranch) { if($cbranch['next'] == $commit['id']) { if($first) { $branch = &$this->data['branches'][$id]; $first = false; } else if($cbranch['id'] == $branch['id']) { } else { $commit['merge'][] = array("point" => $cbranch['id'], "start" => true, "end" => false); $cbranch['active'] = false; if($cbranch['pre_merge']) { $cbranch['pre_merge_start'] = true; $cbranch['pre_merge_id'] = $branch['id']; $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1]; } } } } unset($cbranch); if($first) { $this->data['branches'][count($this->data['branches'])] = array(); $branch = &$this->data['branches'][count($this->data['branches'])-1]; $branch['id'] = $brach_id++; $branch['uid'] = $branch_uid++; $branch['active'] = true; $branch['pre_merge'] = false; } } if(array_key_exists('parent', $commit) && count($commit['parent']) > 1) { //merge(s) for($j = 1; $j < count($commit['parent']); $j++) { $add = true; foreach($this->data['branches'] as $cbranch) { if(array_key_exists('next', $cbranch) && $cbranch['next'] == $commit['parent'][$j]) { $add = false; break; } } if($add) { $cadd = true; foreach($this->data['branches'] as $bid => &$cbranch) { if(!$cbranch['active']) { $cadd = false; break; } } if($cadd) { $this->data['branches'][count($this->data['branches'])] = array(); $cbranch = &$this->data['branches'][count($this->data['branches'])-1]; $cbranch['id'] = $brach_id++; } $cbranch['uid'] = $branch_uid++; $cbranch['active'] = true; $cbranch['pre_merge'] = true; $cbranch['next'] = $commit['parent'][$j]; } $commit['merge'][] = array("point" => $cbranch['id'], "start" => false, "end" => $add); $commit['dot_type'] = self::DOT_TYPE_MERGE; $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1]; unset($cbranch); } } else if(!array_key_exists('parent', $commit) || count($commit['parent']) == 0) { $branch['active'] = false; $commit['dot_type'] = self::DOT_TYPE_INIT; } $branch['next'] = (array_key_exists('parent', $commit) ? $commit['parent'][0] : null); $branch['pre_merge'] = false; $this->data['ubranches'][$branch['uid']] = $this->data['branches'][$branch['id']-1]; $commit['dot'] = $branch['id']; foreach($this->data['branches'] as $id => $cbranch) { $commit['branches'][$id] = $cbranch; } $this->graph[$commit['id']] = $this->graph_data($commit); //echo$commit['id']." ".$this->get_graph($commit['id'])."\n"; } } private function graph_data($commit) { $data = array(); $data['d'] = array(); $data['d']['p'] = $commit['dot']; //dot position $data['d']['type'] = 'a'; $data['l'] = array(); //lines $data['br'] = array(); //branches for color check foreach($commit['branches'] as $branch) { if($branch['pre_merge'] || $commit['merge']) { $data['br'][] = $branch['uid']; } if($branch['active']) { if($commit['dot'] == $branch['id']) continue; $show = true; if($commit['merge']) { foreach($commit['merge'] as $merge) { if($merge['point'] == $branch['id']) { $show = false; break; } } } if(!$show) continue; if($branch['id'] > $this->max_branches) continue; $data['l'][] = $branch['id']; } } $data['m'] = array(); //merges if($commit['merge']) { foreach($commit['merge'] as $merge) { $mergepoint = array(); $mergepoint['hl'] = array(); $mergepoint['p'] = $merge['point']; if($commit['dot'] <= $this->max_branches) $mergepoint['dd'] = ($commit['dot'] < $merge['point'] ? 'r' : 'l'); else $mergepoint['dd'] = 'n'; $mergepoint['ml'] = ($merge['start'] ? 1 : 0) + ($merge['end'] ? 2 : 0); if($merge['point'] <= $this->max_branches) $mergepoint['md']=($commit['dot'] < $merge['point'] ? 'l' : 'r'); else $mergepoint['md'] = 'n'; $min = ($commit['dot'] < $merge['point'] ? $commit['dot'] : $merge['point']) + 1; $max = ($commit['dot'] < $merge['point'] ? $merge['point'] : $commit['dot']); for($i = $min; $i < $max; $i++) { if($i > $this->max_branches) continue; $mergepoint['hl'][] = $i; } $data['m'][] = $mergepoint; } } $data['d']['type'] = $commit['dot_type']; return $data; } public function get_graph($id) { if(!GitConfig::GITGRAPH_ENABLE) return; $graph = $this->graph[$id]; $data = $graph['d']['p'].$graph['d']['type'].count($this->data['branches']).'('.implode(',', $graph['l']).')'; $first_merge = true; foreach($graph['m'] as $merge) { if(!$first_merge) $data .= '|'; $first_merge = false; $data.=$merge['p'].$merge['dd'].$merge['md'].$merge['ml']; foreach($merge['hl'] as $hline) $data.=','.$hline; } $graph['cs'] = array(); foreach($graph['br'] as $buid) { if(!$buid) continue; $branch = $this->data['ubranches'][$buid]; if($branch['pre_merge'] && array_key_exists('pre_merge_start', $branch) && $branch['pre_merge_start']) $graph['cs'][] = $branch['id']."=".$branch['pre_merge_id']; } if(count($graph['cs'])) { $data .= '('.implode(',', $graph['cs']).')'; } if(GitConfig::GITGRAPH_BASE64) $data = base64_encode($data); return $data; } public function get_header_graph() { $data = "head:"; $branchcount = 0; $dataadd = ""; foreach($this->data['branches'] as $branch) { if(array_key_exists('sticky', $branch) && $branch['sticky']) { $name = explode('/', $branch['name'][0], 3); $dataadd .= "\n".$branch['id'].":".$name[2]; $branchcount++; } } $data.=$branchcount.$dataadd; if(GitConfig::GITGRAPH_BASE64) $data = base64_encode($data); return $data; } } class graph_image_generator { private $max_branches; private $image; private $size; private $tile_size; private $header_height = false; private $colors, $color_swap = array(); public function __construct() { $this->max_branches = GitConfig::GITGRAPH_MAX_BRANCHES; $this->size = GitConfig::GITGRAPH_END_SIZE; $this->tile_size = GitConfig::GITGRAPH_TILE_SIZE; $this->colors = array( NULL, array(255, 0, 0), array(array(0, 255, 0), array(0, 192, 0)), array(0, 0, 255), array(128, 128, 128), array(128, 128, 0), array(0, 128, 128), array(128, 0, 128) ); } public function generate($data) { if(!GitConfig::GITGRAPH_ENABLE) return; $data = $this->parse_data($data); if(!$data) return; $count = $data['count']; if($count > $this->max_branches) $count = $this->max_branches; $this->image = imagecreatetruecolor($count * $this->size, $this->size); $transparentIndex = imagecolorallocate($this->image, 0xFF, 0xFF, 0xFF); imagefill($this->image, 0, 0, $transparentIndex); $this->apply_data($data); imagecolortransparent($this->image, $transparentIndex); header('Content-Type: image/png'); imagepng($this->image); imagedestroy($this->image); } private function display_header($header) { $header = explode("\n",$header); $count = $header[0]; $header = array_slice($header, 1); if($count > $this->max_branches) $count = $this->max_branches; if(!$this->header_height) { $maxlen = 0; foreach($header as $head) { $head = explode(":", $head, 2); $name = $head[1]; if(strlen($name) > $maxlen) $maxlen = strlen($name); } $this->header_height = $maxlen * 2 + 15; } $image = imagecreatetruecolor($count * $this->size + 60, $this->header_height); $transparentIndex = imagecolorallocate($image, 217, 216, 209); imagefill($image, 0, 0, $transparentIndex); $branches = 0; foreach($header as $head) { $head = explode(":", $head, 2); $color = $this->get_color($head[0], true); $name = $head[1]; $branches++; $color = imagecolorallocatealpha($image, $color[0], $color[1], $color[2], 0); imagettftext($image, 8, 28, ($head[0]-1) * $this->size + 10, $this->header_height-2, $color, realpath(dirname(__FILE__)."/../")."/res/arial.ttf", $name); } if(!$branches) die(); imagecolortransparent($image, $transparentIndex); header('Content-Type: image/png'); imagepng($image); imagedestroy($image); } private function parse_data($data) { if(GitConfig::GITGRAPH_BASE64) $data = base64_decode($data); if(!preg_match("/^([0-9]+)([abc]{1})([0-9]+)\(([^\)]*)\)([a-z0-9,\|]*)(\(([^\)]*)\)|)/i", $data, $matches)) { if(preg_match("/head:(.*)/i", $data, $matches)) { $this->display_header(substr($data, strlen("head:"))); } return null; } $data = array(); $data['dot'] = array(); $data['dot']['pos'] = $matches[1]; $data['dot']['type'] = $matches[2]; $data['count'] = $matches[3]; if($matches[4] != '') $data['l'] = explode(',', $matches[4]); else $data['l'] = array(); $data['m'] = array(); if($matches[5] != '') { foreach(explode('|', $matches[5]) as $m) { $merge = array(); if(!preg_match("/^([0-9]+)([rln]{1})([rln]{1})([0123]{1})(.*)/i", $m, $sm)) return null; $merge['hl'] = array(); $merge['pos'] = $sm[1]; $merge['dd'] = $sm[2]; $merge['md'] = $sm[3]; $merge['ml'] = $sm[4]; if($sm[5] != '') { $merge['hl'] = explode(',', $sm[5]); } $data['m'][] = $merge; } } if($matches[6] != '' && $matches[7] != '') { foreach(explode(',', $matches[7]) as $cswap) { $cswap = explode("=", $cswap); $this->color_swap[$cswap[0]] = $cswap[1]; } } return $data; } function image_set_color($src, $color) { imagesavealpha($src, true); imagealphablending($src, false); // scan image pixels for ($x = 0; $x < $this->size; $x++) { for ($y = 0; $y < $this->size; $y++) { $src_pix = imagecolorat($src,$x,$y); $src_pix_array = imagecolorsforindex($src, $src_pix); imagesetpixel($src, $x, $y, imagecolorallocatealpha($src, $color[0], $color[1], $color[2], $src_pix_array['alpha'])); } } } function overlay_image($name, $left, $color = false) { $image2 = imagecreatefrompng($name); if($color) { $this->image_set_color($image2, $color); } imagecopyresampled($this->image, $image2, $left, 0, 0, 0, $this->size, $this->size, $this->tile_size, $this->tile_size); } function get_color($id, $text = false) { if(array_key_exists($id, $this->color_swap)) $id = $this->color_swap[$id]; $color_array = $this->colors[($id - 1) % count($this->colors)]; if($text && is_array($color_array[0]) && $color_array[1]) return $color_array[1]; return (is_array($color_array[0]) ? $color_array[0] : $color_array); } private function apply_data($data) { foreach($data['l'] as $l) $this->overlay_image("res/line.png", ($l-1) * $this->size, $this->get_color($l)); foreach($data['m'] as $m) { if($m['dd'] == 'r') $this->overlay_image("res/dot_merge_right.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos'])); else if($m['dd'] == 'l') $this->overlay_image("res/dot_merge_left.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos'])); if($m['md'] == 'r') $this->overlay_image("res/".(($m['ml'] & 1) ? "branch" : "merge")."_right.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos'])); else if($m['md'] == 'l') $this->overlay_image("res/".(($m['ml'] & 1) ? "branch" : "merge")."_left.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos'])); if($m['ml'] == 0) $this->overlay_image("res/line.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos'])); foreach($m['hl'] as $hl) { $this->overlay_image("res/line_h.png", ($hl - 1) * $this->size, $this->get_color($m['pos'])); } } if($data['dot']['type'] == 'a') $this->overlay_image("res/dot.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos'])); else if($data['dot']['type'] == 'b') $this->overlay_image("res/dot_merge.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos'])); else if($data['dot']['type'] == 'c') $this->overlay_image("res/dot_init.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos'])); } } ?>