how to resize an SVG with Imagick/ImageMagick

你离开我真会死。 提交于 2019-11-30 05:04:39

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;
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!