Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
645 views
in Technique[技术] by (71.8m points)

php - how to resize an SVG with Imagick/ImageMagick

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...