Convert SVG image to PNG with PHP
Asked Answered
A

9

126

I'm working on a web project that involves a dynamically generated map of the US coloring different states based on a set of data.

This SVG file gives me a good blank map of the US and is very easy to change the color of each state. The difficulty is that IE browsers don't support SVG so in order for me to use the handy syntax the svg offers, I'll need to convert it to a JPG.

Ideally, I'd like to do this with only the GD2 library but could also use ImageMagick. I have absolutely no clue how to do this.

Any solution that would allow me to dynamically change the colors of states on a map of the US will be considered. The key is that it is easy to change the colors on the fly and that it is cross browser. PHP/Apache solutions only, please.

Anchovy answered 26/1, 2011 at 19:42 Comment(1)
are there any classes designed to port SVG over to VML? that way you could still have a 'HTML5'-type solutionPlow
N
164

That's funny you asked this, I just did this recently for my work's site and I was thinking I should write a tutorial... Here is how to do it with PHP/Imagick, which uses ImageMagick:

$usmap = '/path/to/blank/us-map.svg';
$im = new Imagick();
$svg = file_get_contents($usmap);

/*loop to color each state as needed, something like*/ 
$idColorArray = array(
     "AL" => "339966"
    ,"AK" => "0099FF"
    ...
    ,"WI" => "FF4B00"
    ,"WY" => "A3609B"
);

foreach($idColorArray as $state => $color){
//Where $color is a RRGGBB hex value
    $svg = preg_replace(
         '/id="'.$state.'" style="fill:#([0-9a-f]{6})/'
        , 'id="'.$state.'" style="fill:#'.$color
        , $svg
    );
}

$im->readImageBlob($svg);

/*png settings*/
$im->setImageFormat("png24");
$im->resizeImage(720, 445, imagick::FILTER_LANCZOS, 1);  /*Optional, if you need to resize*/

/*jpeg*/
$im->setImageFormat("jpeg");
$im->adaptiveResizeImage(720, 445); /*Optional, if you need to resize*/

$im->writeImage('/path/to/colored/us-map.png');/*(or .jpg)*/
$im->clear();
$im->destroy();

the steps regex color replacement may vary depending on the svg path xml and how you id & color values are stored. If you don't want to store a file on the server, you can output the image as base 64 like

<?php echo '<img src="data:image/jpg;base64,' . base64_encode($im) . '"  />';?>

(before you use clear/destroy) but ie has issues with PNG as base64 so you'd probably have to output base64 as jpeg

you can see an example here I did for a former employer's sales territory map:

Start: https://upload.wikimedia.org/wikipedia/commons/1/1a/Blank_US_Map_(states_only).svg

Finish: enter image description here

Edit

Since writing the above, I've come up with 2 improved techniques:

1) instead of a regex loop to change the fill on state , use CSS to make style rules like

<style type="text/css">
#CA,#FL,HI{
    fill:blue;
}
#Al, #NY, #NM{
    fill:#cc6699;
}
/*etc..*/
</style>

and then you can do a single text replace to inject your css rules into the svg before proceeding with the imagick jpeg/png creation. If the colors don't change, check to make sure you don't have any inline fill styles in your path tags overriding the css.

2) If you don't have to actually create a jpeg/png image file (and don't need to support outdated browsers), you can manipulate the svg directly with jQuery. You can't access the svg paths when embedding the svg using img or object tags, so you'll have to directly include the svg xml in your webpage html like:

<div>
<?php echo file_get_contents('/path/to/blank/us-map.svg');?>
</div>

then changing the colors is as easy as:

<script type="text/javascript" src="/path/to/jquery.js"></script>
<script type="text/javascript">
    $('#CA').css('fill', 'blue');
    $('#NY').css('fill', '#ff0000');
</script>
Nert answered 26/1, 2011 at 20:17 Comment(12)
Thanks for the very exact and useful tutorial on how to do this. I'll certainly use your solution as a backup but am eager to try and just get svg compatibility across all major browsers.Anchovy
SVG is not supported in ie8 or lower without requiring the user to install an svg viewer plugin - from SVG Wikipedia page: "All major modern web browsers, support and render SVG markup directly with the very notable exception of Microsoft Internet Explorer (IE)[3] The Internet Explorer 9 beta supports the basic SVG feature set.[4] Currently, support for browsers running under Android is also limited."Nert
Yes, but svgweb seems to iron out all the incompatibilities using a little bit of js and flash. That's the solution I went with.Anchovy
I like your clean and fast solution. Personally when interacting with xml files I prefer using a dom parser to feel safer than with regex. Sth like: $dom = new DOMDocument(); $dom->loadXML( $svg ); $dom->getElementsByTagName('image')->item(0)->setAttribute('id', $state); $svg = $dom->saveXML();Wellbred
an xml parser would be a safer, albeit slightly slower, solution with any other svg... in this case the regex is safe because I made sure each state's attributes were formatted exactly as (id="XX" style="fill:#XXXXXX").Nert
@Nert what is $idColorArray? What colors it contains? How this array looks like?Friesen
@AwaisQarni Sorry I forgot to include that. The keys are the state IDs and the values are the hexadecimal color values. I added in a partial example of $idColorArray to make this clear.Nert
Clean & Clear SolutionAcotyledon
@Nert bro i need help with converting image to svg vector please help me , ready to pay for your time please add me on skype : gauravkr.mishra90National
@National Thanks for the offer but turning an image into SVG vector is a much more complicated process than turning an SVG into an image. Unfortunately I do not know how to do that so I wouldn't be able to helpNert
in my case i got terrible resolution images because i was upscaling the svg. however setting the image size before loading the svg solved this: https://mcmap.net/q/81998/-set-density-parameter-for-imagick-with-phpRillis
Great answer. If you also want to preserve the transparent background, check this answer https://mcmap.net/q/179424/-convert-svg-image-to-png-with-phpSally
W
18

When converting SVG to transparent PNG, don't forget to put this BEFORE $imagick->readImageBlob():

$imagick->setBackgroundColor(new ImagickPixel('transparent'));
Workaday answered 20/11, 2012 at 21:25 Comment(3)
How is that possible to call that method before reading the image, I am receiving an error "Can not process empty Imagick object". And yes my imagick extension is installed as it is working and converting images.Iapetus
You are a good man my friend. This wasn't asked but much needed for the main answer. Have a good day.Tigrinya
this should be included in the chosen answer.Sagamore
Z
11

You mention that you are doing this because IE doesn't support SVG.

The good news is that IE does support vector graphics. Okay, so it's in the form of a language called VML which only IE supports, rather than SVG, but it is there, and you can use it.

Google Maps, among others, will detect the browser capabilities to determine whether to serve SVG or VML.

Then there's the Raphael library, which is a Javascript browswer-based graphics library, which supports either SVG or VML, again depending on the browser.

Another one which may help: SVGWeb.

All of which means that you can support your IE users without having to resort to bitmap graphics.

See also the top answer to this question, for example: XSL Transform SVG to VML

Zenger answered 26/1, 2011 at 20:1 Comment(1)
+1 for mentioning raphael, which is definitely a good solution and worth investigating for its excellent implementation of cross browser vector graphics.Nicobarese
H
5

This is a method for converting a svg picture to a gif using standard php GD tools

1) You put the image into a canvas element in the browser:

<canvas id=myCanvas></canvas>

<script>
var Key='picturename'
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
base_image = new Image();
base_image.src = myimage.svg;
base_image.onload = function(){

    //get the image info as base64 text string

    var dataURL = canvas.toDataURL();
    //Post the image (dataURL) to the server using jQuery post method
    $.post('ProcessPicture.php',{'TheKey':Key,'image': dataURL ,'h': canvas.height,'w':canvas.width,"stemme":stemme } ,function(data,status){ alert(data+' '+status) });
}
</script>    

And then convert it at the server (ProcessPicture.php) from (default) png to gif and save it. (you could have saved as png too then use imagepng instead of image gif):

//receive the posted data in php
$pic=$_POST['image'];
$Key=$_POST['TheKey'];
$height=$_POST['h'];
$width=$_POST['w'];
$dir='../gif/'
$gifName=$dir.$Key.'.gif';
 $pngName=$dir.$Key.'.png';

//split the generated base64 string before the comma. to remove the 'data:image/png;base64, header  created by and get the image data
$data = explode(',', $pic);
$base64img = base64_decode($data[1]);
$dimg=imagecreatefromstring($base64img); 

//in order to avoid copying a black figure into a (default) black background you must create a white background

$im_out = ImageCreateTrueColor($width,$height);
$bgfill = imagecolorallocate( $im_out, 255, 255, 255 );
imagefill( $im_out, 0,0, $bgfill );

//Copy the uploaded picture in on the white background
ImageCopyResampled($im_out, $dimg ,0, 0, 0, 0, $width, $height,$width, $height);

//Make the gif and png file 
imagegif($im_out, $gifName);
imagepng($im_out, $pngName);
Hartung answered 31/5, 2018 at 11:58 Comment(4)
I support this answer because it was the only PHP-solution that really worked also for more complex SVG-images. All libraries incl. Imagick failed with wrong rendering. This solution is complex but it can be automated and has the best results.Psychoneurosis
@Psychoneurosis haave you testet his solution? Test it first, because is not working if the base64 string is from a SVG image.Jocose
The SVG-picture must be below 700 kByte, which is the post limit.Hartung
inside onload don't forget to put the image! context.drawImage(base_image, 0, 0)Fabrication
S
4

This is v. easy, have been doing work on this for the past few weeks.

You need the Batik SVG Toolkit. Download, and place the files in the same directory as the SVG you want to convert to a JPEG, also make sure you unzip it first.

Open the terminal, and run this command:

java -jar batik-rasterizer.jar -m image/jpeg -q 0.8 NAME_OF_SVG_FILE.svg

That should output a JPEG of the SVG file. Really easy. You can even just place it in a loop and convert loads of SVGs,

import os

svgs = ('test1.svg', 'test2.svg', 'etc.svg') 
for svg in svgs:
    os.system('java -jar batik-rasterizer.jar -m image/jpeg -q 0.8 '+str(svg)+'.svg')
Selfabsorbed answered 26/1, 2011 at 20:42 Comment(2)
This is great. Thanks for the tip. I'm going to use it in conjunction with perl to batch-process loads of SVG files I created from a template.Harker
Downvoted this answer because it does not use PHP as requested in the original question.Knowable
P
3

I would like to share my answer too it might help someone.

This it is more for simple case when your svg dose not contain fill style and by default black and you want to convert it to png and add color to result png.

function convertSvgToPng($svgPath, $fillColor, $outPath)
{
    $im = new Imagick();
    $svg = file_get_contents($svgPath);
    
    // !!! THIS is the trick part - just appending to all <path fill color
    $svg = str_replace('<path ', '<path style="fill:'.$fillColor.'" ', $svg);
    
    $im->readImageBlob($svg);       
    $im->setImageFormat("png24");

    $im->writeImage($outPath);
    $im->clear();
    $im->destroy();
}
Perdurable answered 6/9, 2022 at 6:56 Comment(1)
I downvoted because you didn't show an example of how to call it. Posting a function by itself does not constitute a good answer. An example of the SVG format your function expects would make the answer actually useful.Madly
G
2

I do not know of a standalone PHP / Apache solution, as this would require a PHP library that can read and render SVG images. I'm not sure such a library exists - I don't know any.

ImageMagick is able to rasterize SVG files, either through the command line or the PHP binding, IMagick, but seems to have a number of quirks and external dependencies as shown e.g. in this forum thread. I think it's still the most promising way to go, it's the first thing I would look into if I were you.

Gonroff answered 26/1, 2011 at 19:47 Comment(0)
G
-1

You can use Raphaël—JavaScript Library and achieve it easily. It will work in IE also.

Gallstone answered 14/8, 2014 at 7:26 Comment(1)
Add details present in the link. Link may break any timeSpermophile
C
-1
$command = 'convert -density 300 ';
                        if(Input::Post('height')!='' && Input::Post('width')!=''){
                            $command.='-resize '.Input::Post('width').'x'.Input::Post('height').' ';
                        }
                        $command.=$svg.' '.$source;
                        exec($command);
                        @unlink($svg);

or using : potrace demo :Tool4dev.com

Cheery answered 7/10, 2015 at 5:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.