diff --git a/trunk/svgtoimage.php b/trunk/svgtoimage.php index 282a0d9..60cd553 100644 --- a/trunk/svgtoimage.php +++ b/trunk/svgtoimage.php @@ -2,33 +2,35 @@ // TODO // ajouter header thanks to http://fr.php.net/manual/fr/function.image-type-to-mime-type.php // prendre en compte l'opacité grâce à imagecolorallocatealpha ? -// conversion des couleurs "black" , "blue", etc en #000 etc. // stroke-width pour tous \o/ // pour les rectangles avec point ou tiret http://fr.php.net/manual/fr/function.imagesetstyle.php +// ajout de title include 'log.php'; + class SVGTOIMAGE{ - protected $_svg; protected $_svgXML; protected $_image; - protected $_format; protected $_log; protected $_x; protected $_y; protected $_width; protected $_height; protected $_showDesc = false; + protected $_desc; private $transparentColor = array(0,0,255); public $_debug = true; - //const TIRET = array($red, $red, $red, $red, $red, IMG_COLOR_TRANSPARENT, IMG_COLOR_TRANSPARENT, IMG_COLOR_TRANSPARENT, IMG_COLOR_TRANSPARENT, IMG_COLOR_TRANSPARENT); - + /* array of color names => hex color + because some svg creator uses them + */ private $colors = array( 'black' => '#000000', 'red' => '#FF0000', 'white' => '#FFFFFF', 'turquoise' => '#00FFFF', + 'grey' => '#CCCCCC', 'light grey' => '#C0C0C0', 'light blue' => '#0000FF', 'dark grey' => '#808080', @@ -45,27 +47,46 @@ class SVGTOIMAGE{ 'grass green' => '#408080', ); - public function __construct($svg, $format = 'png'){ + /* + * constructor + * parse the svg with simplexml + */ + public function __construct($svg){ if($this->_debug) $this->_log = new Log('log.dat'); - $this->_svg = $svg; if($this->_debug) $this->_log->message('Ouverture du fichier contentant : '.$svg); - $this->_svgXML = simplexml_load_string($this->_svg); - $this->_format = $format; + $this->_svgXML = simplexml_load_string($svg); } + /* + * Construct with a file + * @param : string path to the file + * @return : instance of this class + */ public static function load($file){ $svg = file_get_contents($file); return new SVGTOIMAGE($svg); } + /* + * Construct with a string + * @param : string ... + * @return : instance of this class + */ public static function parse($xml){ return new SVGTOIMAGE($xml); } + /* + * Destroy the GD Image when finished + */ public function __destruct(){ imagedestroy($this->_image); } + /* + * setter - option : show the description from the svg into the image if present + * @param boolean + */ public function setShowDesc($showDesc = true){ if(is_bool($showDesc)){ if($this->_debug) $this->_log->message('Passage de showDesc en '.$showDesc); @@ -75,6 +96,10 @@ class SVGTOIMAGE{ } } + /* + * setter - option : origin of the final image from the svg (default : 0) + * @param int + */ public function setX($x){ if(is_int($x)){ if($this->_debug) $this->_log->message('Passage de x en '.$x); @@ -84,6 +109,10 @@ class SVGTOIMAGE{ } } + /* + * setter - option : origin of the final image from the svg (default : 0) + * @param int + */ public function setY($y){ if(is_int($y)){ if($this->_debug) $this->_log->message('Passage de y en '.$y); @@ -93,6 +122,10 @@ class SVGTOIMAGE{ } } + /* + * setter - option : width of the final image (default : svg width) + * @param int + */ public function setWidth($width){ if(is_int($width)){ if($this->_debug) $this->_log->message('Passage de width en '.$width); @@ -102,6 +135,10 @@ class SVGTOIMAGE{ } } + /* + * setter - option : height of the final image (default : svg height) + * @param int + */ public function setHeight($height){ if(is_int($height)){ if($this->_debug) $this->_log->message('Passage de height en '.$height); @@ -120,15 +157,24 @@ class SVGTOIMAGE{ return $imageSize; } + /* + * @return int final image width + */ private function _getImageWidth(){ return isset($this->_width) ? $this->_width : $this->_svgXML->attributes()->width; } + /* + * @return int final image height + */ private function _getImageHeight(){ return isset($this->_height) ? $this->_height : $this->_svgXML->attributes()->height; } - + /* + * @param string Color code (ie: #CCC , #FE4323, etc...) + * @return array with R | G | B + */ private function _parseColor($colorCode){ if(strlen($colorCode) == 7){ if($this->_debug) $this->_log->message('Parse Color '.$colorCode); @@ -150,8 +196,13 @@ class SVGTOIMAGE{ return array(0,0,0); } + /* + * Allocate color to the final image thanks to _parseColor (check if the color isn't spelled directly 'black') + * @param string color code + * @return imageallocate on the image + */ private function _allocateColor($color){ - if($color != '' && array_key_exists($color, $this->colors)){ + if($color != '' && array_key_exists(strtolower($color), $this->colors)){ $arrayColor = $this->_parseColor($this->colors[$color]); }else{ $arrayColor = $this->_parseColor($color); @@ -159,12 +210,18 @@ class SVGTOIMAGE{ return imagecolorallocate( $this->_image, $arrayColor[0], $arrayColor[1], $arrayColor[2] ); } + /* + * add the given image from svg to the final image + * @param simpleXMLElement + * @return imagecopy + */ private function _parseImage($imageNode){ $x = 0; $y = 0; $width = 0; $height = 0; $href = ''; + $transform = ''; $r = 0; foreach($imageNode->attributes() as $name => $value){ switch($name){ @@ -174,10 +231,23 @@ class SVGTOIMAGE{ case 'height': $height = $value; break; case 'href': case 'xlink:href':$href = $value; break; - case 'r' : $r = $value; break; + //case 'r' : $r = $value; break; // no ! + case 'transform': $transform = $value; case 'style' : if(strripos($value, 'display: none') || strripos($value, 'display:none')) return; break; } } + if($transform != ''){ + $transforms = split('[()]', $transform); + $nb = count($transforms); + for($i = 0; $i < $nb; $i++){ + // rotation + if($transforms[$i] == 'rotate'){ + $rotinfo = $transforms[$i+1]; + $rotinfo = explode(' ', $rotinfo); + $r = $rotinfo[0]; + } + } + } if($width == 0 || $height == 0 || $href == '') return; if($this->_debug) $this->_log->message('Image - x : '.$x.' - y : '.$y.' - largeur : '.$width.' - longueur : '.$height.' - url : '.$href.' - angle : '.$r); @@ -208,15 +278,27 @@ class SVGTOIMAGE{ imagecolortransparent($newImage, $blue); } - imagecopy($this->_image,$newImage,$x,$y,0,0,imagesx($newImage) , imagesy($newImage)); + imagecopy($this->_image,$newImage,$x,$y,0,0, $width , $height); + //imagecopy($this->_image,$newImage,$x,$y,0,0,imagesx($newImage) , imagesy($newImage)); } + /* + * Check if the given SVG xml is W3C valid + * DEPRECATED + * @param string ... + * @return boolean + */ private function _pathIsW3C($path){ if(strripos($path, ',')) return true; return false; } + /* + * small function to find int into a string - works like java parseint + * @param string containing numbers + * @return int + */ private function _parseInt($string){ if(preg_match('/(\d+)/', $string, $array)) { return $array[1]; @@ -225,6 +307,12 @@ class SVGTOIMAGE{ } } + /* + * add a line to the final image + * @param $x1, $y1, $x2, $y2 int position of segment + * @param imagecolorallocate color (via _allocatecolor !) + * @return imageline + */ private function _drawLine($x1, $y1, $x2, $y2, $color){ if(!imageline( $this->_image , $x1, $y1, $x2, $y2, $color )){ if($this->_debug) $this->_log->error('Chemin erroné : '.$x1.' - '.$y1.' - '.$x2.' - '.$y2); @@ -233,9 +321,12 @@ class SVGTOIMAGE{ } } + /* + * add path/lineS/polyline whatever you name it. + * @param simpleXMLElement + * @return lines on the final image via _drawLine + */ private function _parsePath($pathNode){ - // - // // imagesetbrush // imagesetstyle (pour dotted, dashed etc) $path = ''; @@ -244,14 +335,14 @@ class SVGTOIMAGE{ $stroke = ''; foreach($pathNode->attributes() as $name=>$value){ switch($name){ - case 'd': $path = $value; break; + case 'd': case 'points': $path = $value; break; case 'stroke': $stroke = $value; break; case 'fill': $fill = ($value == 'none') ? '' : $value; break; case 'stroke-width' : $strokeWidth = $value; break; case 'style' : if(strripos($value, 'display: none') || strripos($value, 'display:none')) return; break; } } - if(substr($path, 0,1) != 'M'){ + if(substr($path, 0,1) != 'M' && !is_numeric(substr($path, 0,1))){ if($this->_debug) $this->_log->error('Mauvais path rencontré : '.$path); return; } @@ -302,6 +393,40 @@ class SVGTOIMAGE{ $lastOpe = 'V'; $i++; + }elseif(substr($pathArray[$i], 0, 1) == 'C'){ + + $control1x = $this->_parseInt($pathArray[$i]); + $control1y = $this->_parseInt($pathArray[$i+1]); + $control2x = $this->_parseInt($pathArray[$i+2]); + $control2y = $this->_parseInt($pathArray[$i+3]); + $newX = $this->_parseInt($pathArray[$i+4]); + $newY = $this->_parseInt($pathArray[$i+5]); + + // Algorithme de http://www.dreamstube.com/post/Bezier-Curves-In-PHP!.aspx ne fonctionne pas ! + $cx=3*($control1x-$lastX); + $bx=3*($control2x-$control1x)-$cx; + $ax=$newX-$lastX-$cx-$bx; + + $cy=3*($control1y-$lastY); + $by=3*($control2y-$control1y)-$cy; + $ay=$newY-$lastY-$cy-$by; + if($this->_debug) $this->_log->message('ax : '.$ax.', ay : '.$ay); + $function_x='('.$ax.')*$t*$t*$t+('.$bx.')*$t*$t+('.$cx.')*$t+'.$lastX; + $function_y='('.$ay.')*$t*$t*$t+('.$by.')*$t*$t+('.$cy.')*$t+'.$lastY; + $function_z=2; + $j=0; + for($t=0; $t<1; $t+=.01) + { + eval('$x_points[$j]=1*'.$function_x.';'); + eval('$y_points[$j]=1*'.$function_y.';'); + eval('$z_points[$j]=1*'.$function_z.';'); + if($this->_debug) $this->_log->message('cx : '.$x_points[$j].', cy : '.$y_points[$j]*(-1).' d: '.$z_points[$j]); + imagearc($this->_image, $x_points[$j]+$this->_getImageWidth()/2, ($y_points[$j]*(-1))+$this->_getImageHeight()/2, $z_points[$j], $z_points[$j], 0, 360, $colorStroke); + $j++; + } + $lastX = $newX; + $lastY = $newY; + $i=$i+6; }elseif(is_numeric(substr($pathArray[$i], 0, 1))){ switch($lastOpe){ case 'L': @@ -310,6 +435,7 @@ class SVGTOIMAGE{ $this->_drawLine($lastX , $lastY , $newX , $newY , $colorStroke); $lastX = $newX; $lastY = $newY; + $i=$i+2; break; case 'H': $newX = $this->_parseInt($pathArray[$i]); @@ -327,9 +453,12 @@ class SVGTOIMAGE{ if($this->_debug) $this->_log->error('2 bouclages dans une boucle'); $i++; break; - default : + default : //polyline if($this->_debug) $this->_log->error('last opé inconnue '.$lastOpe); - $i++; + $lastX = $this->_parseInt($pathArray[$i]); + $lastY = $this->_parseInt($pathArray[$i+1]); + $lastOpe = 'L'; + $i=$i+2; break; } @@ -338,7 +467,7 @@ class SVGTOIMAGE{ $lastOpe = 'Z'; //utile? $i++; }else - $i++; // au cas où. + $i++; // au cas où pour éviter une boucle infinie. if($this->_debug) $this->_log->message('counter :'.$i); } imagecolordeallocate( $this->_image, $colorStroke); @@ -346,6 +475,11 @@ class SVGTOIMAGE{ imagesetthickness ( $this->_image , 1 ); } + /* + * add a circle in the final image + * @param SimpleXMLElement + * @return + */ private function _parseCircle($circleNode){ $x = 0; $y = 0; @@ -386,8 +520,12 @@ class SVGTOIMAGE{ imagesetthickness ( $this->_image , 1 ); } + /* + * add a rectangle to the final image + * @param simpleXMLElement + * @return a nice rectangle ! + */ private function _parseRectangle($rectNode){ - // $x = 0; $y = 0; $width = 0; @@ -398,7 +536,6 @@ class SVGTOIMAGE{ $strokeWidth = 1; foreach($rectNode->attributes() as $name => $value){ switch($name){ - // imagesetthickness ( resource $image , int $thickness ) // imagesetstyle (pour dotted, dashed etc) case 'x': $x = $value; break; case 'y': $y = $value; break; @@ -419,7 +556,7 @@ class SVGTOIMAGE{ if($this->_debug && !$thickness) $this->_log->error('Erreur dans la mise en place de l\'épaisseur du trait'); if($this->_debug) $this->_log->message('Rectangle - x : '.$x.' - y : '.$y.' - width : '.$width.' - height : '.$height.' - fill : '.$colorFill[0].'-'.$colorFill[1].'-'.$colorFill[2].' - stroke : '.$colorStroke[0].'-'.$colorStroke[1].'-'.$colorStroke[2]); if($fill == ''){ - imagerectangle($this->_image , $x , $y , $x+$width , $y+$height, $colorStroke); //resource $image , int $x1 , int $y1 , int $x2 , int $y2 , int $color + imagerectangle($this->_image , $x , $y , $x+$width , $y+$height, $colorStroke); }else{ imagefilledrectangle ($this->_image , $x , $y , $x+$width , $y+$height, $colorFill ); } @@ -428,32 +565,110 @@ class SVGTOIMAGE{ imagesetthickness ( $this->_image , 1 ); } + /* + * add a polygon in the final image + * @param simpleXMLElement + * @return po-po-po-polygon ! + */ + private function _parsePolygon($polyNode){ + $points = ''; + $fill = ''; + $stroke = ''; + $strokeWidth = 1; + foreach($polyNode->attributes() as $name => $value){ + switch($name){ + // imagesetstyle (pour dotted, dashed etc) + case 'points' : $points = $value; break; + case 'fill': $fill = ($value == 'none') ? '' : $value; break; + case 'stroke': $stroke = $value; break; + case 'stroke-width' : $strokeWidth = $value; break; + case 'style' : if(strripos($value, 'display: none') || strripos($value, 'display:none')) return; break; + } + } + if($points == '') + return; + $pointArray = split('[ ,]', $points); + $colorStroke = $this->_allocateColor((string)$stroke); + $colorFill = $this->_allocateColor((string)$fill); + $thickness = imagesetthickness( $this->_image , (int)$strokeWidth ); + if($this->_debug && !$thickness) $this->_log->error('Erreur dans la mise en place de l\'épaisseur du trait'); + + if($fill == ''){ + imagepolygon ( $this->_image , $pointArray , count($pointArray)/2 , $colorStroke ); + }else{ + imagefilledpolygon ($this->_image , $pointArray , count($pointArray)/2 , $colorFill ); + } + imagecolordeallocate($this->_image,$colorStroke); + imagecolordeallocate($this->_image,$colorFill); + imagesetthickness ( $this->_image , 1 ); + } + + /* + * add the description text in the final image + * @param string the description + * @return boolean + */ private function _parseDescription($desc){ if($this->_debug) $this->_log->message('Ajout de la description : '.$desc); return imagestring ( $this->_image , 2, 10, $this->_getImageHeight()-20, $desc , imagecolorallocate($this->_image, 255, 255, 255)); } + + /* + * ignore group for the moment + * acts like ungrouped + */ + private function _parseGroup($groupNode){ + foreach($groupNode->children() as $element){ + $this->_chooseParse($element); + } + } + + /* + * select what to parse + * @param simpleXMLElement + * @return the selected function + */ + private function _chooseParse($element){ + if($element->getName() == 'image') + $this->_parseImage($element); + if($element->getName() == 'circle') + $this->_parseCircle($element); + if($element->getName() == 'rect') + $this->_parseRectangle($element); + if($element->getName() == 'path') + $this->_parsePath($element); + if($element->getName() == 'polygon') + $this->_parsePolygon($element); + if($element->getName() == 'polyline') + $this->_parsePath($element); + if($element->getName() == 'title') + $this->_parseTitle($element); + if($element->getName() == 'desc' && $this->_showDesc) + $this->_desc = $element; + } - public function toImage(){ - //$test = Imagick::__construct('http://labs.shikiryu.com/experimental-cut/images/pieces/2.png'); + /* + * parse everything, main function + * @param string format of the ouput 'png' 'gif' jpg' + * @param string path where you want to save the file (with the final name), null will just show the image but not saved on server + * @return the image + */ + public function toImage($format = 'png', $path = null){ $writeDesc = null; $this->_image = imagecreatetruecolor($this->_getImageWidth(), $this->_getImageHeight()); imagealphablending($this->_image, true); //imageantialias($this->_image, true); // On ne peut pas gérer l'épaisseur des traits si l'antialiasing est activé... lol ? foreach($this->_svgXML->children() as $element){ - if($element->getName() == 'image') - $this->_parseImage($element); - if($element->getName() == 'circle') - $this->_parseCircle($element); - if($element->getName() == 'rect') - $this->_parseRectangle($element); - if($element->getName() == 'path') - $this->_parsePath($element); - if($element->getName() == 'desc' && $this->_showDesc) - $writeDesc = $element; + $this->_chooseParse($element); } - if($writeDesc) $this->_parseDescription($writeDesc); + if($this->_showDesc && $this->_desc != null) $this->_parseDescription($this->_desc); //imagefilter ( $this->_image , IMG_FILTER_SMOOTH, 6); - return imagepng($this->_image); + switch($format){ + case 'png' : + default : + //header('Content-type: image/png'); + return imagepng($this->_image, $path); + } } } \ No newline at end of file diff --git a/trunk/test.php b/trunk/test.php index 263a6fa..8e0ee4b 100644 --- a/trunk/test.php +++ b/trunk/test.php @@ -3,6 +3,20 @@ include 'svgtoimage.php'; /* + */ $svg = ' Created with Raphael @@ -13,6 +27,10 @@ $svg = ' + + + '; $svgtoimage = SVGTOIMAGE::parse($svg);