continued :)
[phpgitweb.git] / htdocs / lib / graph.class.php
1 <?php
2 /* graph.class.php - phpgitweb
3  * Copyright (C) 2011-2012  Philipp Kreil (pk910)
4  * 
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.
9  * 
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.
14  * 
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/>. 
17  */
18
19 class graph_data_generator {
20         const DOT_TYPE_NORMAL = 'a';
21         const DOT_TYPE_MERGE = 'b';
22         const DOT_TYPE_INIT = 'c';
23
24         private $data = array();
25         private $graph = array();
26         private $max_branches, $brach_id = 1, $branch_id = 1;
27         
28         public function __construct() {
29                 $this->max_branches = 10;
30                 $this->data['branches'] = array();
31                 $this->data['ubranches'] = array();
32         }
33         
34         public function add_branch($first_id, $name) {
35                 $existing = false;
36                 foreach($this->data['branches'] as &$branch) {
37                         if($branch['next'] == $first_id) {
38                                 $existing = true;
39                                 $branch['name'][] = $name;
40                                 break;
41                         }
42                 }
43                 unset($branch);
44                 if($existing)
45                         continue;
46                 $this->data['branches'][count($this->data['branches'])] = array(
47                         "id" => $this->brach_id++,
48                         "uid" => $this->branch_uid++,
49                         "active" => true,
50                         "sticky" => true,
51                         "name" => array($name),
52                         "next" => $first_id,
53                         "pre_merge" => false
54                 );
55         }
56         
57         public function parse($commits) {
58                 $brach_id = $this->brach_id;
59                 $branch_uid = $this->branch_id;
60                 $first_commit = (count($this->data['branches']) == 0 ? true : false);
61                 foreach($commits as $commit) {
62                         //get current branch
63                         $commit['merge'] = array();
64                         $commit['dot_type'] = self::DOT_TYPE_NORMAL;
65                         if($first_commit) {
66                                 $first_commit = false;
67                                 $this->data['branches'][0] = array();
68                                 $branch = &$this->data['branches'][0];
69                                 $branch['id'] = $brach_id++;
70                                 $branch['uid'] = $branch_uid++;
71                                 $branch['active'] = true;
72                         } else {
73                                 $first = true;
74                                 foreach($this->data['branches'] as $id => &$cbranch) {
75                                         if($cbranch['next'] == $commit['id']) {
76                                                 if($first && !$cbranch['pre_merge']) {
77                                                         $branch = &$this->data['branches'][$id];
78                                                         $first = false;
79                                                 }
80                                         }
81                                 }
82                                 foreach($this->data['branches'] as $id => &$cbranch) {
83                                         if($cbranch['next'] == $commit['id']) {
84                                                 if($first) {
85                                                         $branch = &$this->data['branches'][$id];
86                                                         $first = false;
87                                                 } else if($cbranch['id'] == $branch['id']) {
88                                                 } else {
89                                                         $commit['merge'][] = array("point" => $cbranch['id'], "start" => true, "end" => false);
90                                                         $cbranch['active'] = false;
91                                                         if($cbranch['pre_merge']) {
92                                                                 $cbranch['pre_merge_start'] = true;
93                                                                 $cbranch['pre_merge_id'] = $branch['id'];
94                                                                 $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1];
95                                                         }
96                                                 }
97                                         }
98                                 }
99                                 unset($cbranch);
100                                 if($first) {
101                                         $this->data['branches'][count($this->data['branches'])] = array();
102                                         $branch = &$this->data['branches'][count($this->data['branches'])-1];
103                                         $branch['id'] = $brach_id++;
104                                         $branch['uid'] = $branch_uid++;
105                                         $branch['active'] = true;
106                                         $branch['pre_merge'] = false;
107                                         
108                                 }
109                         }
110                         
111                         if(array_key_exists('parent', $commit) && count($commit['parent']) > 1) {
112                                 //merge(s)
113                                 for($j = 1; $j < count($commit['parent']); $j++) {
114                                         $add = true;
115                                         foreach($this->data['branches'] as $cbranch) {
116                                                 if($cbranch['next'] == $commit['parent'][$j]) {
117                                                         $add = false;
118                                                         break;
119                                                 }
120                                         }
121                                         if($add) {
122                                                 $cadd = true;
123                                                 foreach($this->data['branches'] as $bid => &$cbranch) {
124                                                         if(!$cbranch['active']) {
125                                                                 $cadd = false;
126                                                                 break;
127                                                         }
128                                                 }
129                                                 if($cadd) {
130                                                         $this->data['branches'][count($this->data['branches'])] = array();
131                                                         $cbranch = &$this->data['branches'][count($this->data['branches'])-1];
132                                                         $cbranch['id'] = $brach_id++;
133                                                 }
134                                                 $cbranch['uid'] = $branch_uid++;
135                                                 $cbranch['active'] = true;
136                                                 $cbranch['pre_merge'] = true;
137                                                 $cbranch['next'] = $commit['parent'][$j];
138                                         }
139                                         $commit['merge'][] = array("point" => $cbranch['id'], "start" => false, "end" => $add);
140                                         $commit['dot_type'] = self::DOT_TYPE_MERGE;
141                                         $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1];
142                                         unset($cbranch);
143                                 }
144                         } else if(!array_key_exists('parent', $commit) || count($commit['parent']) == 0) {
145                                 $branch['active'] = false;
146                                 $commit['dot_type'] = self::DOT_TYPE_INIT;
147                         }
148                         $branch['next'] = (array_key_exists('parent', $commit) ? $commit['parent'][0] : null);
149                         $branch['pre_merge'] = false;
150                         $this->data['ubranches'][$branch['uid']] = $this->data['branches'][$branch['id']-1];
151                         
152                         $commit['dot'] = $branch['id'];
153                         
154                         foreach($this->data['branches'] as $id => $cbranch) {
155                                 $commit['branches'][$id] = $cbranch;
156                         }
157                         
158                         $this->graph[$commit['id']] = $this->graph_data($commit);
159                         //echo$commit['id']." ".$this->get_graph($commit['id'])."\n";
160                 }
161         }
162         
163         private function graph_data($commit) {
164                 $data = array();
165                 $data['d'] = array();
166                 $data['d']['p'] = $commit['dot']; //dot position
167                 $data['d']['type'] = 'a';
168                 $data['l'] = array(); //lines
169                 $data['br'] = array(); //branches for color check
170                 foreach($commit['branches'] as $branch) {
171                         if($branch['pre_merge'] || $commit['merge']) {
172                                 $data['br'][] = $branch['uid'];
173                         }
174                         if($branch['active']) {
175                                 if($commit['dot'] == $branch['id']) continue;
176                                 $show = true;
177                                 if($commit['merge']) {
178                                         foreach($commit['merge'] as $merge) {
179                                                 if($merge['point'] == $branch['id']) {
180                                                         $show = false;
181                                                         break;
182                                                 }
183                                         }
184                                 }
185                                 if(!$show) continue;
186                                 if($branch['id'] > $this->max_branches) continue;
187                                 $data['l'][] = $branch['id'];
188                         }
189                 }
190                 $data['m'] = array(); //merges
191                 if($commit['merge']) {
192                         foreach($commit['merge'] as $merge) {
193                                 $mergepoint = array();
194                                 $mergepoint['hl'] = array();
195                                 $mergepoint['p'] = $merge['point'];
196                                 
197                                 if($commit['dot'] <= $this->max_branches)
198                                         $mergepoint['dd'] = ($commit['dot'] < $merge['point'] ? 'r' : 'l');
199                                 else
200                                         $mergepoint['dd'] = 'n';
201                                 
202                                 $mergepoint['ml'] = ($merge['start'] ? 1 : 0) + ($merge['end'] ? 2 : 0);
203                                 if($merge['point'] <= $this->max_branches)
204                                         $mergepoint['md']=($commit['dot'] < $merge['point'] ? 'l' : 'r');
205                                 else
206                                         $mergepoint['md'] = 'n';
207                                 $min = ($commit['dot'] < $merge['point'] ? $commit['dot'] : $merge['point']) + 1;
208                                 $max = ($commit['dot'] < $merge['point'] ? $merge['point'] : $commit['dot']);
209                                 for($i = $min; $i < $max; $i++) {
210                                         if($i > $this->max_branches) continue;
211                                         $mergepoint['hl'][] = $i;
212                                 }
213                                 $data['m'][] = $mergepoint;
214                         }
215                 }
216                 $data['d']['type'] = $commit['dot_type'];
217                 return $data;
218         }
219         
220         public function get_graph($id) {
221                 $graph = $this->graph[$id];
222                 $data = $graph['d']['p'].$graph['d']['type'].count($this->data['branches']).'('.implode(',', $graph['l']).')';
223                 $first_merge = true;
224                 foreach($graph['m'] as $merge) {
225                         if(!$first_merge)
226                                 $data .= '|';
227                         $first_merge = false;
228                         $data.=$merge['p'].$merge['dd'].$merge['md'].$merge['ml'];
229                         foreach($merge['hl'] as $hline)
230                                 $data.=','.$hline;
231                 }
232                 $graph['cs'] = array();
233                 foreach($graph['br'] as $buid) {
234                         $branch = $this->data['ubranches'][$buid];
235                         if($branch['pre_merge'] && $branch['pre_merge_start'])
236                                 $graph['cs'][] = $branch['id']."=".$branch['pre_merge_id'];
237                 }
238                 if(count($graph['cs'])) {
239                         $data .= '('.implode(',', $graph['cs']).')';
240                 }
241                 return $data;
242         }
243         
244 }
245
246 class graph_image_generator {
247         private $max_branches = 10;
248         private $image;
249         private $size = 20;
250         private $tile_size = 20;
251         private $colors, $color_swap = array();
252         
253         public function generate($data) {
254                 $data = $this->parse_data($data);
255                 if(!$data)
256                         return;
257                 
258                 $this->colors = array(
259                         NULL,
260                         array(255, 0, 0),
261                         array(array(0, 255, 0), array(0, 192, 0)),
262                         array(0, 0, 255),
263                         array(128, 128, 128),
264                         array(128, 128, 0),
265                         array(0, 128, 128),
266                         array(128, 0, 128)
267                 );
268                 
269                 $count = $data['count'];
270                 if($count > $this->max_branches)
271                         $count = $this->max_branches;
272                 $this->image = imagecreatetruecolor($count * $this->size, $this->size);
273                 $transparentIndex = imagecolorallocate($this->image, 0xFF, 0xFF, 0xFF);
274                 imagefill($this->image, 0, 0, $transparentIndex);
275                 
276                 $this->apply_data($data);
277                 
278                 imagecolortransparent($this->image, $transparentIndex);
279
280                 header('Content-Type: image/png');
281                 imagepng($this->image);
282                 imagedestroy($this->image);
283         }
284         
285         private function parse_data($data) {
286                 //$data = array();
287                 if(!preg_match("/^([0-9]+)([abc]{1})([0-9]+)\(([^\)]*)\)([a-z0-9,\|]*)(\(([^\)]*)\)|)/i", $data, $matches))
288                         return null;
289                 
290                 $data = array();
291                 $data['dot'] = array();
292                 $data['dot']['pos'] = $matches[1];
293                 $data['dot']['type'] = $matches[2];
294                 $data['count'] = $matches[3];
295                 
296                 if($matches[4] != '')
297                         $data['l'] = explode(',', $matches[4]);
298                 else
299                         $data['l'] = array();
300                 
301                 $data['m'] = array();
302                 if($matches[5] != '') {
303                         foreach(explode('|', $matches[5]) as $m) {
304                                 $merge = array();
305                                 
306                                 if(!preg_match("/^([0-9]+)([rln]{1})([rln]{1})([0123]{1})(.*)/i", $m, $sm))
307                                         return null;
308                                 $merge['hl'] = array();
309                                 $merge['pos'] = $sm[1];
310                                 $merge['dd'] = $sm[2];
311                                 $merge['md'] = $sm[3];
312                                 $merge['ml'] = $sm[4];
313                                 if($sm[5] != '') {
314                                         $merge['hl'] = explode(',', $sm[5]);
315                                 }
316                                 $data['m'][] = $merge;
317                         }
318                 }
319                 if($matches[6] != '' && $matches[7] != '') {
320                         foreach(explode(',', $matches[7]) as $cswap) {
321                                 $cswap = explode("=", $cswap);
322                                 $this->color_swap[$cswap[0]] = $cswap[1];
323                         }
324                 }
325                 return $data;
326         }
327         
328         function image_set_color($src, $color) {
329                 imagesavealpha($src, true);
330                 imagealphablending($src, false);
331                 // scan image pixels
332                 for ($x = 0; $x < $this->size; $x++) {
333                         for ($y = 0; $y < $this->size; $y++) {
334                                 $src_pix = imagecolorat($src,$x,$y);
335                                 $src_pix_array = imagecolorsforindex($src, $src_pix);
336                                 
337                                 imagesetpixel($src, $x, $y, imagecolorallocatealpha($src, $color[0], $color[1], $color[2], $src_pix_array['alpha']));
338                         }
339                 }
340         }
341
342         function overlay_image($name, $left, $color = false) {
343                 $image2 = imagecreatefrompng($name);
344
345                 if($color) {
346                         $this->image_set_color($image2, $color);
347                 }
348                 imagecopyresampled($this->image, $image2, $left, 0, 0, 0, $this->size, $this->size, $this->tile_size, $this->tile_size);
349         }
350
351         function get_color($id, $text = false) {
352                 if(array_key_exists($id, $this->color_swap))
353                         $id = $this->color_swap[$id];
354                 $color_array = $this->colors[($id - 1) % count($this->colors)];
355                 if($text && is_array($color_array[0]) && $color_array[1])
356                         return $color_array[1];
357                 return (is_array($color_array[0]) ? $color_array[0] : $color_array);
358         }
359         
360         private function apply_data($data) {
361                 foreach($data['l'] as $l)
362                         $this->overlay_image("img/line.png", ($l-1) * $this->size, $this->get_color($l));
363                 foreach($data['m'] as $m) {
364                         if($m['dd'] == 'r')
365                                 $this->overlay_image("img/dot_merge_right.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos']));
366                         else if($m['dd'] == 'l')
367                                 $this->overlay_image("img/dot_merge_left.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos']));
368                         if($m['md'] == 'r')
369                                 $this->overlay_image("img/".(($m['ml'] & 1) ? "branch" : "merge")."_right.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
370                         else if($m['md'] == 'l')
371                                 $this->overlay_image("img/".(($m['ml'] & 1) ? "branch" : "merge")."_left.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
372                         if($m['ml'] == 0)
373                                 $this->overlay_image("img/line.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
374                         foreach($m['hl'] as $hl) {
375                                 $this->overlay_image("img/line_h.png", ($hl - 1) * $this->size, $this->get_color($m['pos']));
376                         }
377                 }
378                 if($data['dot']['type'] == 'a')
379                         $this->overlay_image("img/dot.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
380                 else if($data['dot']['type'] == 'b')
381                         $this->overlay_image("img/dot_merge.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
382                 else if($data['dot']['type'] == 'c')
383                         $this->overlay_image("img/dot_init.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
384                 
385         }
386         
387 }
388
389 ?>