I am sending a string representation of an SVG file to the server and using Imagick to turn this into a jpeg in the following manner:
$image = stripslashes($_POST['json']);
$filename = $_POST['filename'];
$unique = time();
$im = new Imagick();
$im->readImageBlob($image);
$im->setImageFormat("jpeg");
$im->writeImage('../photos/' . $type . '/humourised_' . $unique . $filename);
$im->clear();
$im->destroy();
However I wish to resize the SVG prior to rasterizing it so the the resulting image is larger than the dimensions specified within the SVG file.
I modified my code to the following:
$image = stripslashes($_POST['json']);
$filename = $_POST['filename'];
$unique = time();
$im = new Imagick();
$im->readImageBlob($image);
$res = $im->getImageResolution();
$x_ratio = $res['x'] / $im->getImageWidth();
$y_ratio = $res['y'] / $im->getImageHeight();
$im->removeImage();
$im->setResolution($width_in_pixels * $x_ratio, $height_in_pixels * $y_ratio);
$im->readImageBlob($image);
$im->setImageFormat("jpeg");
$im->writeImage('../photos/' . $type . '/humourised_' . $unique . $filename);
$im->clear();
$im->destroy();
This code should work out the resolution and resize the SVG accordingly. It works perfectly if the SVG canvas and it's elements have 'percentage' based widths, however it doesn't appear to work with elements defined in 'px'. Which is unfortunately a requirement.
A typical SVG string that will be sent to the server looks like this:
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/SVG/DTD/svg10.dtd">
<svg id="tempsvg" style="overflow: hidden; position: relative;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="333" version="1.1" height="444">
<image transform="matrix(1,0,0,1,0,0)" preserveAspectRatio="none" x="0" y="0" width="333" height="444" xlink:href="http://www.songbanc.com/assets/embed/photos/full/133578615720079914224f9e7aad9ac871.jpg"></image>
<image transform="matrix(1,0,0,1,0,0)" preserveAspectRatio="none" x="85.5" y="114" width="50" height="38" xlink:href="http://www.songbanc.com/assets/embed/humourise/elements/thumb/thumb_lips4.png"></image>
<path transform="matrix(1,0,0,1,0,0)" fill="none" stroke="#000" d="M110.5,133L140.5,133" stroke-dasharray="- " opacity="0.5"></path>
<circle transform="matrix(1,0,0,1,0,0)" cx="140.5" cy="133" r="5" fill="#000" stroke="#000"></circle>
<path transform="matrix(1,0,0,1,0,0)" fill="none" stroke="#000" d="M110.5,133L110.5,155.8" stroke-dasharray="- " opacity="0.5"></path>
<circle transform="matrix(1,0,0,1,0,0)" cx="110.5" cy="155.8" r="5" fill="#000" stroke="#000"></circle>
<circle transform="matrix(1,0,0,1,0,0)" cx="110.5" cy="133" r="5" fill="#000" stroke="#000"></circle>
</svg>
As you can see the elements that make up this SVG has pixel definition widths and heights (using percentages is unfortunately not an option for this application)
Is there any way around this? Or any other method of converting an SVG to png and rendering it at a given size without loss of quality.
Thanks.
EDIT: Although I never actually managed to find a perfect solution. Instead I ended sending the SVG data as json, looping through it server side and scaling the pixels to the intended height.
Then, after much trial and error I realised that imagemagick had issues wih the standard SVG transform/rotate commands, throwing any manipulated elements out of whack. I ended up switching too 'inkscape' to render the resulting SVG as a rasterised image. And all is well. I'm still digging into a potential formulatic solution to offset the differences that imagemagick makes. If I have any success I will update this question again.
As a workaround of php_imagick's bug, you can scale svg's width=".." and height="..":
function svgScaleHack($svg, $minWidth, $minHeight)
{
$reW = '/(.*<svg[^>]* width=")([\d.]+px)(.*)/si';
$reH = '/(.*<svg[^>]* height=")([\d.]+px)(.*)/si';
preg_match($reW, $svg, $mw);
preg_match($reH, $svg, $mh);
$width = floatval($mw[2]);
$height = floatval($mh[2]);
if (!$width || !$height) return false;
// scale to make width and height big enough
$scale = 1;
if ($width < $minWidth)
$scale = $minWidth/$width;
if ($height < $minHeight)
$scale = max($scale, ($minHeight/$height));
$width *= $scale*2;
$height *= $scale*2;
$svg = preg_replace($reW, "\${1}{$width}px\${3}", $svg);
$svg = preg_replace($reH, "\${1}{$height}px\${3}", $svg);
return $svg;
}
Then you can easily create nice transparent PNG!
createThumbnail('a.svg', 'a.png');
function createThumbnail($filename, $thname, $size=50)
{
$im = new Imagick();
$svgdata = file_get_contents($filename);
$svgdata = svgScaleHack($svgdata, $size, $size);
$im->setBackgroundColor(new ImagickPixel('transparent'));
$im->readImageBlob($svgdata);
$im->setImageFormat("png32");
$im->resizeImage($size, $size, imagick::FILTER_LANCZOS, 1);
file_put_contents($thname, $im->getImageBlob());
$im->clear();
$im->destroy();
}
Note: I've been searching for a solution how to rescale SVG from its initial small size. However it seems that imagick::setResolution is broken. However, ImageMagick library itself is working, so you can use exec('convert...') (might be disabled for security reasons by hosting provider).
So to create thumbnail 50x50 from smaller svg you would do:
convert -density 500 -resize 50 50 -background transparent a.svg PNG32:a.png
I was looking for a solution, and i found this just after reading this post, and work like a charm:
$im = new Imagick();
$im->readImage("/path/to/image.svg");
$res = $im->getImageResolution();
$x_ratio = $res['x'] / $im->getImageWidth();
$y_ratio = $res['y'] / $im->getImageHeight();
$im->removeImage();
$im->setResolution($width_in_pixels * $x_ratio, $height_in_pixels * $y_ratio);
$im->readImage("/path/to/image.svg");
// Now you can do anything with the image, such as convert to a raster image and output it to the browser:
$im->setImageFormat("png");
header("Content-Type: image/png");
echo $im;
Credits go to the author of that comment in php manuals page.
You don't need imagick to complete this task. For example you wont to resize your svg (w: 60px, h: 70px) => (w: 36px, h: 36px) to get an icon for a button.
$svg = file_get_contents($your_svg_file);
// I prefer to use DOM, because it's safer and easier as to use preg_match
$svg_dom = new DOMDocument();
libxml_use_internal_errors(true);
$svg_dom->loadXML($svg);
libxml_use_internal_errors(false);
//get width and height values from your svg
$tmp_obj = $svg_dom->getElementsByTagName('svg')->item(0);
$svg_width = floatval($tmp_obj->getAttribute('width'));
$svg_height = floatval($tmp_obj->getAttribute('height'));
// set width and height of your svg to preferred dimensions
$tmp_obj->setAttribute('width', 36);
$tmp_obj->setAttribute('height', 36);
// check if width and height of your svg is smaller than the width and
// height you set above => no down scaling is needed
if ($svg_width < 36 && $svg_height < 36) {
//center your svg content in new box
$x = abs($svg_width - 36) / 2;
$y = abs($svg_height - 36) / 2;
$tmp_obj->getElementsByTagName('g')->item(0)->setAttribute('transform', "translate($x,$y)");
} else {
// scale down your svg content and center it in new box
$scale = 1;
// set padding to 0 if no gaps are desired
$padding = 2;
// get scale factor
if ($svg_width > $svg_height) {
$scale = (36 - $padding) / $svg_width;
} else {
$scale = (36 - $padding) / $svg_height;
}
$x = abs(($scale * $svg_width) - 36) / 2;
$y = abs(($scale * $svg_height) - 36) / 2;
$tmp_obj->getElementsByTagName('g')->item(0)->setAttribute('transform', "translate($x,$y) scale($scale,$scale)");
file_put_contents('your_new_svg.svg', $svg_dom->saveXML());
}
Be careful by setting translate(x,y), because it may happen that your svg content can be set outside of the box and you will see nothing except the background.
My script above works proper only if your initial translate is set to (0,0). You can use this
$svg_path = $svg_dom->getElementsByTagName('path')->item(0);
$svg_g = $svg_dom->getElementsByTagName('g')->item(0);
$transform = $svg_g->getAttribute('transform');
// get x and y of translate
$transform = substr($transform, strlen('translate('));
$transform = substr($transform, 0, strlen($transform)-1);
$transform_data = explode(',', $transform);
// get path data
$d = $svg_path->getAttribute('d');
$d_data = explode(' ', $d);
$tmp = explode(',', $d_data[1]);
$d_data[1] = ($tmp[0] + $transform_data[0]).','.($tmp[1]+$transform_data[1]);
$svg_path->setAttribute('d', implode(' ', $d_data));
$svg_g->setAttribute('transform','translate(0,0)');
file_put_contents('your_new_svg.svg',$svg_dom->saveXML());
to set translate to (0,0) and adapt path data to new settings because path data depends on translate and vice versa.
I use this two scripts to generate png icons by resizing my svg icons to dimension I need and converting them to png without loss of quality.
I hope it's clear what I mean.
Here is an example on how to take an image that is already in a string (say, from a database), and resize it, add a border, and print it out. I use this for showing reseller logos
// Decode image from base64
$image=base64_decode($imagedata);
// Create Imagick object
$im = new Imagick();
// Convert image into Imagick
$im->readimageblob($image);
// Create thumbnail max of 200x82
$im->thumbnailImage(200,82,true);
// Add a subtle border
$color=new ImagickPixel();
$color->setColor("rgb(220,220,220)");
$im->borderImage($color,1,1);
// Output the image
$output = $im->getimageblob();
$outputtype = $im->getFormat();
header("Content-type: $outputtype");
echo $output;
来源:https://stackoverflow.com/questions/10383305/how-to-resize-an-svg-with-imagick-imagemagick