source: spip-zone/_plugins_/filtres_images_vectorise/trunk/lib/potracio/Potracio.php @ 116199

Last change on this file since 116199 was 116199, checked in by cedric@…, 21 months ago

Un plugin pour vectoriser en SVG des images bitmap, qui propose 4 nouveaux filtres

  • extraire_palette_couleurs permet d'extraire une palette de couleur d'une image (par defaut les 3 couleurs les plus representees) en utilisant un calcul des couleurs dominantes par partitionnement en k-moyennes

`
<BOUCLE_palette(POUR){tableau #FICHIER|extraire_palette_couleurs{3}}>
<div style="display: inline-block;width: 30px;height: 15px;background-color: #VALEUR;"></div>
</BOUCLE_palette>
`

  • image_geometrize permet de creer une image SVG approchante de l'image d'origine en utilisant le lib GeometrizePHP https://github.com/Cerdic/geometrize-php/ (attention methode gourmande en temps de calcul)
  • image_potrace permet de generer un trace SVG depuis l'image d'origine, a l'aide de Potracio PHP qui est un portage PHP de PotRace? https://seenthis.net/messages/645575
  • image_geopotrize combine les 2 techniques : un background geometrize qui n'a pas besoin d'un grand nombre de shapes et un trace potrace superpose/mixe

Les 3 filtres ont tout un tas d'option pour qui veut jouer avec, mais les reglages par defaut permettent d'avoir immediatement un joli resultat exploitable
A noter que pour image_geometrize, le temps de calcul peut depasser les 30s pour generer le nombre de shapes demandees, selon les reglages utilises.
Dans ce cas le calcul est arrete au bout de 20s, on stocke et on renvoie l'image provisoire incomplete, et on stocke l'etat du calcul qui reprendra au prochain calcul de la page.

(J'evite les screenshots dans le message de commit mais le coeur y est)

File size: 35.3 KB
Line 
1<?php
2namespace Potracio;
3/*  Potracio - Port by Otamay (2017) (https://github.com/Otamay/potracio.git)
4 * A PHP Port of Potrace (http://potrace.sourceforge.net),
5 * ported from https://github.com/kilobtye/potrace. Info below:
6 *
7 *  Copyright (C) 2001-2013 Peter Selinger.
8 *
9 * A javascript port of Potrace (http://potrace.sourceforge.net).
10 *
11 * Licensed under the GPL
12 *
13 * Usage
14 *   loadImageFromFile(file) : load image from File
15 *
16 *   setParameter({para1: value, ...}) : set parameters
17 *     parameters:
18 *        turnpolicy ("black" / "white" / "left" / "right" / "minority" / "majority")
19 *          how to resolve ambiguities in path decomposition. (default: "minority")       
20 *        turdsize
21 *          suppress speckles of up to this size (default: 2)
22 *        optcurve (true / false)
23 *          turn on/off curve optimization (default: true)
24 *        alphamax
25 *          corner threshold parameter (default: 1)
26 *        opttolerance
27 *          curve optimization tolerance (default: 0.2)
28 *       
29 *   getSVG(size, opt_type) : return a string of generated SVG image.
30 *                                    result_image_size = original_image_size * size
31 *                                    optional parameter opt_type can be "curve"
32 */
33
34class Point{
35  public $x;
36  public $y;
37 
38  public function __construct($x=NULL, $y=NULL) {
39    if($x !== NULL)
40      $this->x = $x;
41    if($y !== NULL)
42      $this->y = $y;
43  }
44}
45
46class Opti{
47  public $pen = 0;
48  public $c;
49  public $t = 0;
50  public $s = 0;
51  public $alpha = 0;
52 
53  public function __construct(){
54    $this->c = array(new Point(), new Point());
55  }
56}
57
58class Bitmap{
59  public $w;
60  public $h;
61  public $size;
62  public $data;
63
64  public function __construct($w, $h){
65    $this->w = $w;
66    $this->h = $h;
67    $this->size = $w * $h;
68    $this->data = array();
69  }
70
71  public function at($x, $y) {
72    return ($x >= 0 && $x < $this->w && $y >=0 && $y < $this->h) && 
73          $this->data[$this->w * $y + $x] === 1;
74  }
75
76  public function index($i) {
77    $point = new Point();
78    $point->y = floor($i / $this->w);
79    $point->x = $i - $point->y * $this->w;
80    return $point;
81  }
82
83  public function flip($x, $y) {
84    if ($this->at($x, $y)) {
85      $this->data[$this->w * $y + $x] = 0;
86    } else {
87      $this->data[$this->w * $y + $x] = 1;
88    }
89  }
90}
91
92class Path{
93  public $area = 0;
94  public $len = 0;
95  public $curve = array();
96  public $pt = array();
97  public $minX = 100000;
98  public $minY = 100000;
99  public $maxX= -1;
100  public $maxY = -1;
101  public $sum = array();
102  public $lon = array();
103}
104
105class Curve{
106  public $n;
107  public $tag;
108  public $c;
109  public $alphaCurve = 0;
110  public $vertex;
111  public $alpha;
112  public $alpha0;
113  public $beta;
114
115  public function __construct($n){
116    $this->n = $n;
117    $this->tag = array_fill(0, $n, NULL);
118    $this->c = array_fill(0, $n * 3, NULL);
119    $this->vertex = array_fill(0, $n, NULL);
120    $this->alpha = array_fill(0, $n, NULL);
121    $this->alpha0 = array_fill(0, $n, NULL);
122    $this->beta = array_fill(0, $n, NULL);
123  }
124}
125
126class Quad{
127  public $data = array(0,0,0,0,0,0,0,0,0);
128
129  public function at($x, $y) {
130    return $this->data[$x * 3 + $y];
131  }
132}
133
134class Sum{
135  public $x;
136  public $y;
137  public $xy;
138  public $x2;
139  public $y2;
140
141  public function __construct($x, $y, $xy, $x2, $y2) {
142    $this->x = $x;
143    $this->y = $y;
144    $this->xy = $xy;
145    $this->x2 = $x2;
146    $this->y2 = $y2;
147  }
148}
149
150class Potracio{
151  public $imgElement;
152  public $imgCanvas;
153  public $bm = NULL;
154  public $pathlist = array();
155  public $info = array('turnpolicy' => "minority", 
156                       'turdsize' => 2,
157                       'optcurve' => TRUE,
158                       'alphamax' => 1,
159                       'opttolerance' => 0.2);
160
161  public function __construct($data=array()){
162    $this->setParameter($data);
163  }
164
165  public function setParameter($data){
166    $this->info = (object) array_merge((array) $this->info, $data);
167  }
168 
169  public function loadImageFromFile($file){
170    list($w, $h) = getimagesize($file);
171    $image = imagecreatefromstring(file_get_contents($file));
172   
173    $this->bm = new Bitmap($w, $h);
174   
175    for($i=0; $i<$h; $i++){
176      for($j=0; $j<$w; $j++){
177        $rgb = imagecolorat($image, $j, $i);
178        $r = ($rgb >> 16) & 0xFF;
179        $g = ($rgb >> 8) & 0xFF;
180        $b = $rgb & 0xFF;
181        $color = (0.2126 * $r) + (0.7153 * $g) + (0.0721 * $b);
182        $this->bm->data[] = $color < 128 ? 1 : 0;
183      }
184    }
185  }
186
187  private function bmToPathlist(){
188    $info = $this->info;
189    $bm = &$this->bm;
190    $bm1 = clone $bm;
191    $currentPoint = new Point(0, 0);
192   
193    $findNext = function($point) use ($bm1) {
194      $i = $bm1->w * $point->y + $point->x;
195      while ($i < $bm1->size && $bm1->data[$i] !== 1) {
196        $i++;
197      }
198      if($i < $bm1->size)
199        return $bm1->index($i);
200      return false;
201    };
202   
203    $majority = function($x, $y) use ($bm1) {
204      for ($i = 2; $i < 5; $i++) {
205        $ct = 0;
206        for ($a = -$i + 1; $a <= $i - 1; $a++) {
207          $ct += $bm1->at($x + $a, $y + $i - 1) ? 1 : -1;
208          $ct += $bm1->at($x + $i - 1, $y + $a - 1) ? 1 : -1;
209          $ct += $bm1->at($x + $a - 1, $y - $i) ? 1 : -1;
210          $ct += $bm1->at($x - $i, $y + $a) ? 1 : -1;
211        }
212        if ($ct > 0) {
213          return 1;
214        } else if ($ct < 0) {
215          return 0;
216        }
217      }
218      return 0;
219    };
220   
221    $findPath = function($point) use($bm, $bm1, $majority, $info) {
222      $path = new Path();
223      $x = $point->x;
224      $y = $point->y;
225      $dirx = 0; $diry = 1;
226     
227      $path->sign = $bm->at($point->x, $point->y) ? "+" : "-";
228     
229      while (1) {
230        $path->pt[] = new Point($x, $y);
231        if ($x > $path->maxX)
232          $path->maxX = $x;
233        if ($x < $path->minX)
234          $path->minX = $x;
235        if ($y > $path->maxY)
236          $path->maxY = $y;
237        if ($y < $path->minY)
238          $path->minY = $y;
239        $path->len++;
240       
241        $x += $dirx;
242        $y += $diry;
243        $path->area -= $x * $diry;
244       
245        if ($x === $point->x && $y === $point->y)
246          break;
247       
248        $l = $bm1->at($x + ($dirx + $diry - 1 ) / 2, $y + ($diry - $dirx - 1) / 2);
249        $r = $bm1->at($x + ($dirx - $diry - 1) / 2, $y + ($diry + $dirx - 1) / 2);
250       
251        if ($r && !$l) {
252          if ($info->turnpolicy === "right" ||
253              ($info->turnpolicy === "black" && $path->sign === '+') ||
254              ($info->turnpolicy === "white" && $path->sign === '-') ||
255              ($info->turnpolicy === "majority" && $majority($x, $y)) ||
256              ($info->turnpolicy === "minority" && !$majority($x, $y))) {
257            $tmp = $dirx;
258            $dirx = - $diry;
259            $diry = $tmp;
260          } else {
261            $tmp = $dirx;
262            $dirx = $diry;
263            $diry = - $tmp;
264          }
265        } else if ($r) {
266          $tmp = $dirx;
267          $dirx = - $diry;
268          $diry = $tmp;
269        } else if (!$l) {
270          $tmp = $dirx;
271          $dirx = $diry;
272          $diry = - $tmp;
273        }
274      }
275      return $path;
276    };
277   
278    $xorPath = function ($path) use(&$bm1){
279      $y1 = $path->pt[0]->y;
280      $len = $path->len;
281     
282      for ($i = 1; $i < $len; $i++) {
283        $x = $path->pt[$i]->x;
284        $y = $path->pt[$i]->y;
285       
286        if ($y !== $y1) {
287          $minY = $y1 < $y ? $y1 : $y;
288          $maxX = $path->maxX;
289          for ($j = $x; $j < $maxX; $j++) {
290            $bm1->flip($j, $minY);
291          }
292          $y1 = $y;
293        }
294      }
295    };
296   
297    while ($currentPoint = $findNext($currentPoint)) {
298      $path = $findPath($currentPoint);
299     
300      $xorPath($path);
301     
302      if ($path->area > $info->turdsize) {
303        $this->pathlist[] = $path;
304      }
305    }
306  }
307
308  private function processPath() {
309    $info = $this->info;
310   
311    $mod = function ($a, $n) {
312      return $a >= $n ? $a % $n : ($a>=0 ? $a : $n-1-(-1-$a) % $n);
313    };
314   
315    $xprod = function ($p1, $p2) {
316      return $p1->x * $p2->y - $p1->y * $p2->x;
317    };
318   
319    $cyclic = function ($a, $b, $c) {
320      if ($a <= $c) {
321        return ($a <= $b && $b < $c);
322      } else {
323        return ($a <= $b || $b < $c);
324      }
325    };
326   
327    $sign = function ($i) {
328      return $i > 0 ? 1 : ($i < 0 ? -1 : 0);
329    };
330   
331    $quadform = function ($Q, $w) {
332      $v = array_fill(0, 3, NULL);
333     
334      $v[0] = $w->x;
335      $v[1] = $w->y;
336      $v[2] = 1;
337      $sum = 0.0;
338     
339      for ($i=0; $i<3; $i++) {
340        for ($j=0; $j<3; $j++) {
341          $sum += $v[$i] * $Q->at($i, $j) * $v[$j];
342        }
343      }
344      return $sum;
345    };
346   
347    $interval = function ($lambda, $a, $b) {
348      $res = new Point();
349     
350      $res->x = $a->x + $lambda * ($b->x - $a->x);
351      $res->y = $a->y + $lambda * ($b->y - $a->y);
352      return $res;
353    };
354   
355    $dorth_infty = function ($p0, $p2) use($sign) {
356      $r = new Point();
357     
358      $r->y = $sign($p2->x - $p0->x);
359      $r->x = - $sign($p2->y - $p0->y);
360     
361      return $r;
362    };
363   
364    $ddenom = function ($p0, $p2) use ($dorth_infty){
365      $r = $dorth_infty($p0, $p2);
366     
367      return $r->y * ($p2->x - $p0->x) - $r->x * ($p2->y - $p0->y);
368    };
369   
370    $dpara = function ($p0, $p1, $p2) {
371      $x1 = $p1->x - $p0->x;
372      $y1 = $p1->y - $p0->y;
373      $x2 = $p2->x - $p0->x;
374      $y2 = $p2->y - $p0->y;
375     
376      return $x1 * $y2 - $x2 * $y1;
377    };
378   
379    $cprod = function ($p0, $p1, $p2, $p3) {
380      $x1 = $p1->x - $p0->x;
381      $y1 = $p1->y - $p0->y;
382      $x2 = $p3->x - $p2->x;
383      $y2 = $p3->y - $p2->y;
384     
385      return $x1 * $y2 - $x2 * $y1;
386    };
387   
388    $iprod = function ($p0, $p1, $p2) {
389      $x1 = $p1->x - $p0->x;
390      $y1 = $p1->y - $p0->y;
391      $x2 = $p2->x - $p0->x;
392      $y2 = $p2->y - $p0->y;
393     
394      return $x1 * $x2 + $y1 * $y2;
395    };
396   
397    $iprod1 = function ($p0, $p1, $p2, $p3) {
398      $x1 = $p1->x - $p0->x;
399      $y1 = $p1->y - $p0->y;
400      $x2 = $p3->x - $p2->x;
401      $y2 = $p3->y - $p2->y;
402     
403      return $x1 * $x2 + $y1 * $y2;
404    };
405   
406    $ddist = function ($p, $q) {
407      return sqrt(($p->x - $q->x) * ($p->x - $q->x) + ($p->y - $q->y) * ($p->y - $q->y));
408    };
409   
410    $bezier = function ($t, $p0, $p1, $p2, $p3) {
411      $s = 1 - $t; $res = new Point();
412     
413      $res->x = $s * $s * $s * $p0->x
414              + 3*($s * $s * $t) * $p1->x
415              + 3*($t * $t * $s) * $p2->x
416              + $t * $t * $t * $p3->x;
417     
418      $res->y = $s * $s * $s * $p0->y
419              + 3*($s * $s * $t) * $p1->y
420              + 3*($t * $t * $s) * $p2->y
421              + $t * $t * $t * $p3->y;
422     
423      return $res;
424    };
425   
426    $tangent = function ($p0, $p1, $p2, $p3, $q0, $q1) use($cprod){
427      $A = $cprod($p0, $p1, $q0, $q1);
428      $B = $cprod($p1, $p2, $q0, $q1);
429      $C = $cprod($p2, $p3, $q0, $q1);
430      $a = $A - 2 * $B + $C;
431      $b = -2 * $A + 2 * $B;
432      $c = $A;
433     
434      $d = $b * $b - 4 * $a * $c;
435     
436      if ($a==0 || $d<0) {
437        return -1.0;
438      }
439     
440      $s = sqrt($d);
441
442      if($a == 0){
443        return -1.0;
444      }
445      $r1 = (-$b + $s) / (2 * $a);
446      $r2 = (-$b - $s) / (2 * $a);
447     
448      if ($r1 >= 0 && $r1 <= 1) {
449        return $r1;
450      } else if ($r2 >= 0 && $r2 <= 1) {
451        return $r2;
452      } else {
453        return -1.0;
454      }
455    };
456   
457    $calcSums = function (&$path) {
458      $path->x0 = $path->pt[0]->x;
459      $path->y0 = $path->pt[0]->y;
460     
461      $path->sums = array();
462      $s = &$path->sums;
463      $s[] = new Sum(0, 0, 0, 0, 0);
464      for($i = 0; $i < $path->len; $i++){
465        $x = $path->pt[$i]->x - $path->x0;
466        $y = $path->pt[$i]->y - $path->y0;
467        $s[] = new Sum($s[$i]->x + $x, $s[$i]->y + $y, $s[$i]->xy + $x * $y,
468                       $s[$i]->x2 + $x * $x, $s[$i]->y2 + $y * $y);
469      }
470    };
471   
472    $calcLon = function (&$path) use($mod, $xprod, $sign, $cyclic){
473      $n = $path->len; $pt = &$path->pt;
474      $pivk = array_fill(0, $n, NULL);
475      $nc = array_fill(0, $n, NULL);
476      $ct = array_fill(0, 4, NULL);
477      $path->lon = array_fill(0, $n, NULL);
478     
479      $constraint = array(new Point(), new Point());
480      $cur = new Point();
481      $off = new Point();
482      $dk = new Point();
483     
484      $k = 0;
485      for($i = $n - 1; $i >= 0; $i--){
486        if ($pt[$i]->x != $pt[$k]->x && $pt[$i]->y != $pt[$k]->y) {
487          $k = $i + 1;
488        }
489        $nc[$i] = $k;
490      }
491     
492      for ($i = $n - 1; $i >= 0; $i--) {
493        $ct[0] = $ct[1] = $ct[2] = $ct[3] = 0;
494        $dir = (3 + 3 * ($pt[$mod($i + 1, $n)]->x - $pt[$i]->x) + 
495                ($pt[$mod($i + 1, $n)]->y - $pt[$i]->y)) / 2;
496        $ct[$dir]++;
497       
498        $constraint[0]->x = 0;
499        $constraint[0]->y = 0;
500        $constraint[1]->x = 0;
501        $constraint[1]->y = 0;
502       
503        $k = $nc[$i];
504        $k1 = $i;
505        while (1) {
506          $foundk = 0;
507          $dir =  (3 + 3 * $sign($pt[$k]->x - $pt[$k1]->x) + 
508                   $sign($pt[$k]->y - $pt[$k1]->y)) / 2;
509          $ct[$dir]++;
510         
511          if ($ct[0] && $ct[1] && $ct[2] && $ct[3]) {
512            $pivk[$i] = $k1;
513            $foundk = 1;
514            break;
515          }
516         
517          $cur->x = $pt[$k]->x - $pt[$i]->x;
518          $cur->y = $pt[$k]->y - $pt[$i]->y;
519         
520          if ($xprod($constraint[0], $cur) < 0 || $xprod($constraint[1], $cur) > 0) {
521            break;
522          }
523         
524          if (abs($cur->x) <= 1 && abs($cur->y) <= 1) {
525           
526          } else {
527            $off->x = $cur->x + (($cur->y >= 0 && ($cur->y > 0 || $cur->x < 0)) ? 1 : -1);
528            $off->y = $cur->y + (($cur->x <= 0 && ($cur->x < 0 || $cur->y < 0)) ? 1 : -1);
529            if ($xprod($constraint[0], $off) >= 0) {
530              $constraint[0]->x = $off->x;
531              $constraint[0]->y = $off->y;
532            }
533            $off->x = $cur->x + (($cur->y <= 0 && ($cur->y < 0 || $cur->x < 0)) ? 1 : -1);
534            $off->y = $cur->y + (($cur->x >= 0 && ($cur->x > 0 || $cur->y < 0)) ? 1 : -1);
535            if ($xprod($constraint[1], $off) <= 0) {
536              $constraint[1]->x = $off->x;
537              $constraint[1]->y = $off->y;
538            }
539          }
540          $k1 = $k;
541          $k = $nc[$k1];
542          if (!$cyclic($k, $i, $k1)) {
543            break;
544          }
545        }
546        if ($foundk == 0) {
547          $dk->x = $sign($pt[$k]->x - $pt[$k1]->x);
548          $dk->y = $sign($pt[$k]->y - $pt[$k1]->y);
549          $cur->x = $pt[$k1]->x - $pt[$i]->x;
550          $cur->y = $pt[$k1]->y - $pt[$i]->y;
551         
552          $a = $xprod($constraint[0], $cur);
553          $b = $xprod($constraint[0], $dk);
554          $c = $xprod($constraint[1], $cur);
555          $d = $xprod($constraint[1], $dk);
556         
557          $j = 10000000;
558          if ($b < 0) {
559            $j = floor($a / -$b);
560          }
561          if ($d > 0) {
562            $j = min($j, floor(-$c / $d));
563          }
564          $pivk[$i] = $mod($k1+$j,$n);
565        }
566      }
567     
568      $j=$pivk[$n-1];
569      $path->lon[$n-1]=$j;
570      for ($i=$n-2; $i>=0; $i--) {
571        if ($cyclic($i+1,$pivk[$i],$j)) {
572          $j=$pivk[$i];
573        }
574        $path->lon[$i]=$j;
575      }
576     
577      for ($i=$n-1; $cyclic($mod($i+1,$n),$j,$path->lon[$i]); $i--) {
578        $path->lon[$i] = $j;
579      }
580    };
581   
582    $bestPolygon = function (&$path) use($mod){
583     
584      $penalty3 = function ($path, $i, $j) {
585        $n = $path->len; $pt = $path->pt; $sums = $path->sums;
586        $r = 0;
587        if ($j>=$n) {
588          $j -= $n;
589          $r = 1;
590        }
591       
592        if ($r == 0) {
593          $x = $sums[$j+1]->x - $sums[$i]->x;
594          $y = $sums[$j+1]->y - $sums[$i]->y;
595          $x2 = $sums[$j+1]->x2 - $sums[$i]->x2;
596          $xy = $sums[$j+1]->xy - $sums[$i]->xy;
597          $y2 = $sums[$j+1]->y2 - $sums[$i]->y2;
598          $k = $j+1 - $i;
599        } else {
600          $x = $sums[$j+1]->x - $sums[$i]->x + $sums[$n]->x;
601          $y = $sums[$j+1]->y - $sums[$i]->y + $sums[$n]->y;
602          $x2 = $sums[$j+1]->x2 - $sums[$i]->x2 + $sums[$n]->x2;
603          $xy = $sums[$j+1]->xy - $sums[$i]->xy + $sums[$n]->xy;
604          $y2 = $sums[$j+1]->y2 - $sums[$i]->y2 + $sums[$n]->y2;
605          $k = $j+1 - $i + $n;
606        } 
607       
608        $px = ($pt[$i]->x + $pt[$j]->x) / 2.0 - $pt[0]->x;
609        $py = ($pt[$i]->y + $pt[$j]->y) / 2.0 - $pt[0]->y;
610        $ey = ($pt[$j]->x - $pt[$i]->x);
611        $ex = -($pt[$j]->y - $pt[$i]->y);
612       
613        $a = (($x2 - 2*$x*$px) / $k + $px*$px);
614        $b = (($xy - $x*$py - $y*$px) / $k + $px*$py);
615        $c = (($y2 - 2*$y*$py) / $k + $py*$py);
616       
617        $s = $ex*$ex*$a + 2*$ex*$ey*$b + $ey*$ey*$c;
618       
619        return sqrt($s);
620      };
621     
622      $n = $path->len;
623      $pen = array_fill(0, $n + 1, NULL);
624      $prev = array_fill(0, $n + 1, NULL);
625      $clip0 = array_fill(0, $n, NULL);
626      $clip1 = array_fill(0, $n + 1,  NULL);
627      $seg0 = array_fill(0, $n + 1, NULL);
628      $seg1 = array_fill(0, $n + 1, NULL);
629     
630      for ($i=0; $i<$n; $i++) {
631        $c = $mod($path->lon[$mod($i-1,$n)]-1,$n);
632        if ($c == $i) {
633          $c = $mod($i+1,$n);
634        }
635        if ($c < $i) {
636          $clip0[$i] = $n;
637        } else {
638          $clip0[$i] = $c;
639        }
640      }
641     
642      $j = 1;
643      for ($i=0; $i<$n; $i++) {
644        while ($j <= $clip0[$i]) {
645          $clip1[$j] = $i;
646          $j++;
647        }
648      }
649     
650      $i = 0;
651      for ($j=0; $i<$n; $j++) {
652        $seg0[$j] = $i;
653        $i = $clip0[$i];
654      }
655      $seg0[$j] = $n;
656      $m = $j;
657     
658      $i = $n;
659      for ($j=$m; $j>0; $j--) {
660        $seg1[$j] = $i;
661        $i = $clip1[$i];
662      }
663      $seg1[0] = 0;
664     
665      $pen[0]=0;
666      for ($j=1; $j<=$m; $j++) {
667        for ($i=$seg1[$j]; $i<=$seg0[$j]; $i++) {
668          $best = -1;
669          for ($k=$seg0[$j-1]; $k>=$clip1[$i]; $k--) {
670            $thispen = $penalty3($path, $k, $i) + $pen[$k];
671            if ($best < 0 || $thispen < $best) {
672              $prev[$i] = $k;
673              $best = $thispen;
674            }
675          }
676          $pen[$i] = $best;
677        }
678      }
679      $path->m = $m;
680      $path->po = array_fill(0, $m, NULL);
681     
682      for ($i=$n, $j=$m-1; $i>0; $j--) {
683        $i = $prev[$i];
684        $path->po[$j] = $i;
685      }
686    };
687   
688    $adjustVertices = function (&$path) use($mod, $quadform){
689     
690      $pointslope = function ($path, $i, $j, &$ctr, &$dir) {
691       
692        $n = $path->len; $sums = $path->sums;
693        $r=0;
694       
695        while ($j>=$n) {
696          $j-=$n;
697          $r+=1;
698        }
699        while ($i>=$n) {
700          $i-=$n;
701          $r-=1;
702        }
703        while ($j<0) {
704          $j+=$n;
705          $r-=1;
706        }
707        while ($i<0) {
708          $i+=$n;
709          $r+=1;
710        }
711       
712        $x = $sums[$j+1]->x - $sums[$i]->x + $r * $sums[$n]->x;
713        $y = $sums[$j+1]->y - $sums[$i]->y + $r * $sums[$n]->y;
714        $x2 = $sums[$j+1]->x2 - $sums[$i]->x2 + $r * $sums[$n]->x2;
715        $xy = $sums[$j+1]->xy - $sums[$i]->xy + $r * $sums[$n]->xy;
716        $y2 = $sums[$j+1]->y2 - $sums[$i]->y2 + $r * $sums[$n]->y2;
717        $k = $j+1-$i+$r*$n;
718       
719        $ctr->x = $x/$k;
720        $ctr->y = $y/$k;
721       
722        $a = ($x2-$x*$x/$k)/$k;
723        $b = ($xy-$x*$y/$k)/$k;
724        $c = ($y2-$y*$y/$k)/$k;
725       
726        $lambda2 = ($a + $c+ sqrt(($a - $c)*($a - $c) + 4 * $b * $b))/2;
727       
728        $a -= $lambda2;
729        $c -= $lambda2;
730       
731        if (abs($a) >= abs($c)) {
732          $l = sqrt($a*$a+$b*$b);
733          if ($l!=0) {
734            $dir->x = -$b/$l;
735            $dir->y = $a/$l;
736          }
737        } else {
738          $l = sqrt($c*$c+$b*$b);
739          if ($l!==0) {
740            $dir->x = -$c/$l;
741            $dir->y = $b/$l;
742          }
743        }
744        if ($l==0) {
745          $dir->x = $dir->y = 0; 
746        }
747      };
748     
749      $m = $path->m; $po = $path->po; $n = $path->len; $pt = $path->pt;
750      $x0 = $path->x0; $y0 = $path->y0;
751      $ctr = array_fill(0, $m, NULL); $dir = array_fill(0, $m, NULL);
752      $q = array_fill(0, $m, NULL);
753      $v = array_fill(0, 3, NULL);
754      $s = new Point();
755     
756      $path->curve = new Curve($m);
757     
758      for ($i=0; $i<$m; $i++) {
759        $j = $po[$mod($i+1,$m)];
760        $j = $mod($j-$po[$i],$n)+$po[$i];
761        $ctr[$i] = new Point();
762        $dir[$i] = new Point();
763        $pointslope($path, $po[$i], $j, $ctr[$i], $dir[$i]);
764      }
765     
766      for ($i=0; $i<$m; $i++) {
767        $q[$i] = new Quad();
768        $d = $dir[$i]->x * $dir[$i]->x + $dir[$i]->y * $dir[$i]->y;
769        if ($d == 0.0) {
770          for ($j=0; $j<3; $j++) {
771            for ($k=0; $k<3; $k++) {
772              $q[$i]->data[$j * 3 + $k] = 0;
773            }
774          }
775        } else {
776          $v[0] = $dir[$i]->y;
777          $v[1] = -$dir[$i]->x;
778          $v[2] = - $v[1] * $ctr[$i]->y - $v[0] * $ctr[$i]->x;
779          for ($l=0; $l<3; $l++) {
780            for ($k=0; $k<3; $k++) {
781              if($d != 0){
782                $q[$i]->data[$l * 3 + $k] = $v[$l] * $v[$k] / $d;
783              }else{
784                $q[$i]->data[$l * 3 + $k] = INF; // TODO Hack para evitar división por 0
785              }
786            }
787          }
788        }
789      }
790     
791      for ($i=0; $i<$m; $i++) {
792        $Q = new Quad();
793        $w = new Point();
794       
795        $s->x = $pt[$po[$i]]->x - $x0;
796        $s->y = $pt[$po[$i]]->y - $y0;
797       
798        $j = $mod($i-1,$m);
799       
800        for ($l=0; $l<3; $l++) {
801          for ($k=0; $k<3; $k++) {
802            $Q->data[$l * 3 + $k] = $q[$j]->at($l, $k) + $q[$i]->at($l, $k);
803          }
804        }
805       
806        while(1) {
807         
808          $det = $Q->at(0, 0)*$Q->at(1, 1) - $Q->at(0, 1)*$Q->at(1, 0);
809          if ($det != 0) {
810            $w->x = (-$Q->at(0, 2)*$Q->at(1, 1) + $Q->at(1, 2)*$Q->at(0, 1)) / $det;
811            $w->y = ( $Q->at(0, 2)*$Q->at(1, 0) - $Q->at(1, 2)*$Q->at(0, 0)) / $det;
812            break;
813          }
814         
815          if ($Q->at(0, 0)>$Q->at(1, 1)) {
816            $v[0] = -$Q->at(0, 1);
817            $v[1] = $Q->at(0, 0);
818          } else if ($Q->at(1, 1)) {
819            $v[0] = -$Q->at(1, 1);
820            $v[1] = $Q->at(1, 0);
821          } else {
822            $v[0] = 1;
823            $v[1] = 0;
824          }
825          $d = $v[0] * $v[0] + $v[1] * $v[1];
826          $v[2] = - $v[1] * $s->y - $v[0] * $s->x;
827          for ($l=0; $l<3; $l++) {
828            for ($k=0; $k<3; $k++) {
829              $Q->data[$l * 3 + $k] += $v[$l] * $v[$k] / $d;
830            }
831          }
832        }
833        $dx = abs($w->x-$s->x);
834        $dy = abs($w->y-$s->y);
835        if ($dx <= 0.5 && $dy <= 0.5) {
836          $path->curve->vertex[$i] = new Point($w->x+$x0, $w->y+$y0);
837          continue;
838        }
839       
840        $min = $quadform($Q, $s);
841        $xmin = $s->x;
842        $ymin = $s->y;
843       
844        if ($Q->at(0, 0) != 0.0) {
845          for ($z=0; $z<2; $z++) {
846            $w->y = $s->y-0.5+$z;
847            $w->x = - ($Q->at(0, 1) * $w->y + $Q->at(0, 2)) / $Q->at(0, 0);
848            $dx = abs($w->x-$s->x);
849            $cand = $quadform($Q, $w);
850            if ($dx <= 0.5 && $cand < $min) {
851              $min = $cand;
852              $xmin = $w->x;
853              $ymin = $w->y;
854            }
855          }
856        }
857       
858        if ($Q->at(1, 1) != 0.0) {
859          for ($z=0; $z<2; $z++) {
860            $w->x = $s->x-0.5+$z;
861            $w->y = - ($Q->at(1, 0) * $w->x + $Q->at(1, 2)) / $Q->at(1, 1);
862            $dy = abs($w->y-$s->y);
863            $cand = $quadform($Q, $w);
864            if ($dy <= 0.5 && $cand < $min) {
865              $min = $cand;
866              $xmin = $w->x;
867              $ymin = $w->y;
868            }
869          }
870        }
871       
872        for ($l=0; $l<2; $l++) {
873          for ($k=0; $k<2; $k++) {
874            $w->x = $s->x-0.5+$l;
875            $w->y = $s->y-0.5+$k;
876            $cand = $quadform($Q, $w);
877            if ($cand < $min) {
878              $min = $cand;
879              $xmin = $w->x;
880              $ymin = $w->y;
881            }
882          }
883        }
884       
885        $path->curve->vertex[$i] = new Point($xmin + $x0, $ymin + $y0);
886      }
887    };
888   
889    $reverse = function (&$path) {
890      $curve = &$path->curve; $m = &$curve->n; $v = &$curve->vertex;
891     
892      for ($i=0, $j=$m-1; $i<$j; $i++, $j--) {
893        $tmp = $v[$i];
894        $v[$i] = $v[$j];
895        $v[$j] = $tmp;
896      }
897    };
898   
899    $smooth = function (&$path) use($mod, $interval, $ddenom, $dpara, $info){
900      $m = $path->curve->n; $curve = &$path->curve;
901     
902      for ($i=0; $i<$m; $i++) {
903        $j = $mod($i+1, $m);
904        $k = $mod($i+2, $m);
905        $p4 = $interval(1/2.0, $curve->vertex[$k], $curve->vertex[$j]);
906       
907        $denom = $ddenom($curve->vertex[$i], $curve->vertex[$k]);
908        if ($denom != 0.0) {
909          $dd = $dpara($curve->vertex[$i], $curve->vertex[$j], $curve->vertex[$k]) / $denom;
910          $dd = abs($dd);
911          $alpha = $dd>1 ? (1 - 1.0/$dd) : 0;
912          $alpha = $alpha / 0.75;
913        } else {
914          $alpha = 4/3.0;
915        }
916        $curve->alpha0[$j] = $alpha;
917       
918        if ($alpha >= $info->alphamax) { 
919          $curve->tag[$j] = "CORNER";
920          $curve->c[3 * $j + 1] = $curve->vertex[$j];
921          $curve->c[3 * $j + 2] = $p4;
922        } else {
923          if ($alpha < 0.55) {
924            $alpha = 0.55;
925          } else if ($alpha > 1) {
926            $alpha = 1;
927          }
928          $p2 = $interval(0.5+0.5*$alpha, $curve->vertex[$i], $curve->vertex[$j]);
929          $p3 = $interval(0.5+0.5*$alpha, $curve->vertex[$k], $curve->vertex[$j]);
930          $curve->tag[$j] = "CURVE";
931          $curve->c[3 * $j + 0] = $p2;
932          $curve->c[3 * $j + 1] = $p3;
933          $curve->c[3 * $j + 2] = $p4;
934        }
935        $curve->alpha[$j] = $alpha; 
936        $curve->beta[$j] = 0.5;
937      }
938      $curve->alphacurve = 1;
939    };
940   
941    $optiCurve = function (&$path) use($mod, $ddist, $sign, $cprod, $dpara, $interval, $tangent, $bezier, $iprod, $iprod1, $info){
942      $opti_penalty = function ($path, $i, $j, $res, $opttolerance, $convc, $areac) use($mod, $ddist, $sign, $cprod, $dpara, $interval, $tangent, $bezier, $iprod, $iprod1){
943        $m = $path->curve->n; $curve = $path->curve; $vertex = $curve->vertex;
944        if ($i==$j) {
945          return 1;
946        }
947       
948        $k = $i;
949        $i1 = $mod($i+1, $m);
950        $k1 = $mod($k+1, $m);
951        $conv = $convc[$k1];
952        if ($conv == 0) {
953          return 1;
954        }
955        $d = $ddist($vertex[$i], $vertex[$i1]);
956        for ($k=$k1; $k!=$j; $k=$k1) {
957          $k1 = $mod($k+1, $m);
958          $k2 = $mod($k+2, $m);
959          if ($convc[$k1] != $conv) {
960            return 1;
961          }
962          if ($sign($cprod($vertex[$i], $vertex[$i1], $vertex[$k1], $vertex[$k2])) !=
963            $conv) {
964            return 1;
965          }
966          if ($iprod1($vertex[$i], $vertex[$i1], $vertex[$k1], $vertex[$k2]) <
967            $d * $ddist($vertex[$k1], $vertex[$k2]) * -0.999847695156) {
968            return 1;
969          }
970        }
971       
972        $p0 = clone $curve->c[$mod($i,$m) * 3 + 2];
973        $p1 = clone $vertex[$mod($i+1,$m)];
974        $p2 = clone $vertex[$mod($j,$m)];
975        $p3 = clone $curve->c[$mod($j,$m) * 3 + 2];
976       
977        $area = $areac[$j] - $areac[$i];
978        $area -= $dpara($vertex[0], $curve->c[$i * 3 + 2], $curve->c[$j * 3 + 2])/2;
979        if ($i>=$j) {
980          $area += $areac[$m];
981        }
982       
983        $A1 = $dpara($p0, $p1, $p2);
984        $A2 = $dpara($p0, $p1, $p3);
985        $A3 = $dpara($p0, $p2, $p3);
986       
987        $A4 = $A1+$A3-$A2;   
988       
989        if ($A2 == $A1) {
990          return 1;
991        }
992       
993        $t = $A3/($A3-$A4);
994        $s = $A2/($A2-$A1);
995        $A = $A2 * $t / 2.0;
996       
997        if ($A == 0.0) {
998          return 1;
999        }
1000       
1001        $R = $area / $A;
1002        $alpha = 2 - sqrt(4 - $R / 0.3);
1003       
1004        $res->c[0] = $interval($t * $alpha, $p0, $p1);
1005        $res->c[1] = $interval($s * $alpha, $p3, $p2);
1006        $res->alpha = $alpha;
1007        $res->t = $t;
1008        $res->s = $s;
1009       
1010        $p1 = clone $res->c[0];
1011        $p2 = clone $res->c[1]; 
1012       
1013        $res->pen = 0;
1014       
1015        for ($k=$mod($i+1,$m); $k!=$j; $k=$k1) {
1016          $k1 = $mod($k+1,$m);
1017          $t = $tangent($p0, $p1, $p2, $p3, $vertex[$k], $vertex[$k1]);
1018          if ($t<-0.5) {
1019            return 1;
1020          }
1021          $pt = $bezier($t, $p0, $p1, $p2, $p3);
1022          $d = $ddist($vertex[$k], $vertex[$k1]);
1023          if ($d == 0.0) {
1024            return 1;
1025          }
1026          $d1 = $dpara($vertex[$k], $vertex[$k1], $pt) / $d;
1027          if (abs($d1) > $opttolerance) {
1028            return 1;
1029          }
1030          if ($iprod($vertex[$k], $vertex[$k1], $pt) < 0 ||
1031              $iprod($vertex[$k1], $vertex[$k], $pt) < 0) {
1032            return 1;
1033          }
1034          $res->pen += $d1 * $d1;
1035        }
1036       
1037        for ($k=$i; $k!=$j; $k=$k1) {
1038          $k1 = $mod($k+1,$m);
1039          $t = $tangent($p0, $p1, $p2, $p3, $curve->c[$k * 3 + 2], $curve->c[$k1 * 3 + 2]);
1040          if ($t<-0.5) {
1041            return 1;
1042          }
1043          $pt = $bezier($t, $p0, $p1, $p2, $p3);
1044          $d = $ddist($curve->c[$k * 3 + 2], $curve->c[$k1 * 3 + 2]);
1045          if ($d == 0.0) {
1046            return 1;
1047          }
1048          $d1 = $dpara($curve->c[$k * 3 + 2], $curve->c[$k1 * 3 + 2], $pt) / $d;
1049          $d2 = $dpara($curve->c[$k * 3 + 2], $curve->c[$k1 * 3 + 2], $vertex[$k1]) / $d;
1050          $d2 *= 0.75 * $curve->alpha[$k1];
1051          if ($d2 < 0) {
1052            $d1 = -$d1;
1053            $d2 = -$d2;
1054          }
1055          if ($d1 < $d2 - $opttolerance) {
1056            return 1;
1057          }
1058          if ($d1 < $d2) {
1059            $res->pen += ($d1 - $d2) * ($d1 - $d2);
1060          }
1061        }
1062       
1063        return 0;
1064      };
1065     
1066      $curve = $path->curve; $m = $curve->n; $vert = $curve->vertex;
1067      $pt = array_fill(0, $m + 1, NULL);
1068      $pen = array_fill(0, $m + 1, NULL);
1069      $len = array_fill(0, $m + 1, NULL);
1070      $opt = array_fill(0, $m + 1, NULL);
1071      $o = new Opti();
1072     
1073      $convc = array_fill(0, $m, NULL); $areac = array_fill(0, $m + 1, NULL);
1074     
1075      for ($i=0; $i<$m; $i++) {
1076        if ($curve->tag[$i] == "CURVE") {
1077          $convc[$i] = $sign($dpara($vert[$mod($i-1,$m)], $vert[$i], $vert[$mod($i+1,$m)]));
1078        } else {
1079          $convc[$i] = 0;
1080        }
1081      }
1082     
1083      $area = 0.0;
1084      $areac[0] = 0.0;
1085      $p0 = $curve->vertex[0];
1086      for ($i=0; $i<$m; $i++) {
1087        $i1 = $mod($i+1, $m);
1088        if ($curve->tag[$i1] == "CURVE") {
1089          $alpha = $curve->alpha[$i1];
1090          $area += 0.3 * $alpha * (4-$alpha) *
1091          $dpara($curve->c[$i * 3 + 2], $vert[$i1], $curve->c[$i1 * 3 + 2])/2;
1092          $area += $dpara($p0, $curve->c[$i * 3 + 2], $curve->c[$i1 * 3 + 2])/2;
1093        }
1094        $areac[$i+1] = $area;
1095      }
1096     
1097      $pt[0] = -1;
1098      $pen[0] = 0;
1099      $len[0] = 0;
1100     
1101     
1102      for ($j=1; $j<=$m; $j++) {
1103        $pt[$j] = $j-1;
1104        $pen[$j] = $pen[$j-1];
1105        $len[$j] = $len[$j-1]+1;
1106       
1107        for ($i=$j-2; $i>=0; $i--) {
1108          $r = $opti_penalty($path, $i, $mod($j,$m), $o, $info->opttolerance, $convc, 
1109                             $areac);
1110          if ($r) {
1111            break;
1112          }
1113          if ($len[$j] > $len[$i]+1 ||
1114              ($len[$j] == $len[$i]+1 && $pen[$j] > $pen[$i] + $o->pen)) {
1115            $pt[$j] = $i;
1116            $pen[$j] = $pen[$i] + $o->pen;
1117            $len[$j] = $len[$i] + 1;
1118            $opt[$j] = $o;
1119            $o = new Opti();
1120          }
1121        }
1122      }
1123      $om = $len[$m];
1124      $ocurve = new Curve($om);
1125      $s = array_fill(0, $om, NULL);
1126      $t = array_fill(0, $om, NULL);
1127     
1128      $j = $m;
1129      for ($i=$om-1; $i>=0; $i--) {
1130        if ($pt[$j]==$j-1) {
1131          $ocurve->tag[$i]     = $curve->tag[$mod($j,$m)];
1132          $ocurve->c[$i * 3 + 0]    = $curve->c[$mod($j,$m) * 3 + 0];
1133          $ocurve->c[$i * 3 + 1]    = $curve->c[$mod($j,$m) * 3 + 1];
1134          $ocurve->c[$i * 3 + 2]    = $curve->c[$mod($j,$m) * 3 + 2];
1135          $ocurve->vertex[$i]  = $curve->vertex[$mod($j,$m)];
1136          $ocurve->alpha[$i]   = $curve->alpha[$mod($j,$m)];
1137          $ocurve->alpha0[$i]  = $curve->alpha0[$mod($j,$m)];
1138          $ocurve->beta[$i]    = $curve->beta[$mod($j,$m)];
1139          $s[$i] = $t[$i] = 1.0;
1140        } else {
1141          $ocurve->tag[$i] = "CURVE";
1142          $ocurve->c[$i * 3 + 0] = $opt[$j]->c[0];
1143          $ocurve->c[$i * 3 + 1] = $opt[$j]->c[1];
1144          $ocurve->c[$i * 3 + 2] = $curve->c[$mod($j,$m) * 3 + 2];
1145          $ocurve->vertex[$i] = $interval($opt[$j]->s, $curve->c[$mod($j,$m) * 3 + 2],
1146                                          $vert[$mod($j,$m)]);
1147          $ocurve->alpha[$i] = $opt[$j]->alpha;
1148          $ocurve->alpha0[$i] = $opt[$j]->alpha;
1149          $s[$i] = $opt[$j]->s;
1150          $t[$i] = $opt[$j]->t;
1151        }
1152        $j = $pt[$j];
1153      }
1154     
1155      for ($i=0; $i<$om; $i++) {
1156        $i1 = $mod($i+1,$om);
1157        if(($s[$i] + $t[$i1]) != 0){
1158          $ocurve->beta[$i] = $s[$i] / ($s[$i] + $t[$i1]);
1159        }else{
1160          $ocurve->beta[$i] = INF; // TODO Hack para evitar división por 0
1161        }
1162      }
1163      $ocurve->alphacurve = 1;
1164      $path->curve = $ocurve;
1165    };
1166
1167    for ($i = 0; $i < count($this->pathlist); $i++) {
1168      $path = &$this->pathlist[$i];
1169      $calcSums($path);
1170      $calcLon($path);
1171      $bestPolygon($path);
1172      $adjustVertices($path);
1173     
1174      if ($path->sign === "-") {
1175        $reverse($path);
1176      }
1177     
1178      $smooth($path);
1179     
1180      if ($info->optcurve) {
1181        $optiCurve($path);
1182      }
1183    }
1184  }
1185 
1186  public function process() {
1187    $this->bmToPathlist();
1188    $this->processPath();
1189  }
1190
1191  public function clear() {
1192    $this->bm = null;
1193    $this->pathlist = array();
1194  }
1195
1196  public function getSVG($size, $opt_type='') {
1197    $bm = &$this->bm;
1198    $pathlist = &$this->pathlist;
1199    $path = function($curve) use($size) {
1200     
1201      $bezier = function($i) use($curve, $size) {
1202        $b = 'C ' . number_format($curve->c[$i * 3 + 0]->x * $size, 3, ".", "") . ' ' .
1203             number_format($curve->c[$i * 3 + 0]->y * $size, 3, ".", "") . ',';
1204        $b .= number_format($curve->c[$i * 3 + 1]->x * $size, 3, ".", "") . ' ' .
1205              number_format($curve->c[$i * 3 + 1]->y * $size, 3, ".", "") . ',';
1206        $b .= number_format($curve->c[$i * 3 + 2]->x * $size, 3, ".", "") . ' ' .
1207              number_format($curve->c[$i * 3 + 2]->y * $size, 3, ".", "") . ' ';
1208        return $b;
1209      };
1210     
1211      $segment = function($i) use ($curve, $size) {
1212        $s = 'L ' . number_format($curve->c[$i * 3 + 1]->x * $size, 3, ".", "") . ' ' .
1213             number_format($curve->c[$i * 3 + 1]->y * $size, 3, ".", "") . ' ';
1214        $s .= number_format($curve->c[$i * 3 + 2]->x * $size, 3, ".", "") . ' ' .
1215              number_format($curve->c[$i * 3 + 2]->y * $size, 3, ".", "") . ' ';
1216        return $s;
1217      };
1218
1219      $n = $curve->n;
1220      $p = 'M' . number_format($curve->c[($n - 1) * 3 + 2]->x * $size, 3, ".", "") .
1221           ' ' . number_format($curve->c[($n - 1) * 3 + 2]->y * $size, 3, ".", "") . ' ';
1222
1223      for ($i = 0; $i < $n; $i++) {
1224        if ($curve->tag[$i] === "CURVE") {
1225          $p .= $bezier($i);
1226        } else if ($curve->tag[$i] === "CORNER") {
1227          $p .= $segment($i);
1228        }
1229      }
1230      //p +=
1231      return $p;
1232    };
1233
1234    $w = $bm->w * $size; $h = $bm->h * $size;
1235    $len = count($pathlist);
1236
1237    $svg = '<svg id="svg" version="1.1" width="' . $w . '" height="' . $h .
1238           '" xmlns="http://www.w3.org/2000/svg">';
1239    $svg .= '<path d="';
1240    for ($i = 0; $i < $len; $i++) {
1241      $c = $pathlist[$i]->curve;
1242      $svg .= $path($c);
1243    }
1244    if ($opt_type === "curve") {
1245      $strokec = "black";
1246      $fillc = "none";
1247      $fillrule = '';
1248    } else {
1249      $strokec = "none";
1250      $fillc = "black";
1251      $fillrule = ' fill-rule="evenodd"';
1252    }
1253    $svg .= '" stroke="' . $strokec . '" fill="' . $fillc . '"' . $fillrule . '/></svg>';
1254
1255    return $svg;
1256  }
1257}
1258?>
Note: See TracBrowser for help on using the repository browser.