【问题标题】:how to resize an SVG with Imagick/ImageMagick如何使用 Imagick/ImageMagick 调整 SVG 的大小
【发布时间】:2012-05-10 03:07:43
【问题描述】:

我将 SVG 文件的字符串表示形式发送到服务器,并使用 Imagick 以下列方式将其转换为 jpeg:

$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();

但是,我希望在光栅化 SVG 之前调整其大小,以便生成的图像大于 SVG 文件中指定的尺寸。

我将我的代码修改为以下内容:

$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();

这段代码应该计算出分辨率并相应地调整 SVG 的大小。如果 SVG 画布及其元素具有基于“百分比”的宽度,则它可以完美运行,但它似乎不适用于“px”中定义的元素。不幸的是,这是一项要求。

将发送到服务器的典型 SVG 字符串如下所示:

<?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>

如您所见,构成此 SVG 的元素具有像素定义宽度和高度(遗憾的是,此应用程序不能选择使用百分比)

有没有办法解决这个问题?或任何其他将 SVG 转换为 png 并以给定大小呈现而不损失质量的方法。

谢谢。

编辑:虽然我实际上从未设法找到完美的解决方案。相反,我结束了将 SVG 数据作为 json 发送,在服务器端循环并将像素缩放到预期的高度。

然后,经过多次尝试和错误,我意识到 imagemagick 在标准 SVG 变换/旋转命令方面存在问题,使任何被操纵的元素都失控了。我最终切换了“inkscape”以将生成的 SVG 渲染为光栅化图像。一切都很好。我仍在研究一种潜在的公式化解决方案,以抵消 imagemagick 造成的差异。如果我有任何成功,我会再次更新这个问题。

【问题讨论】:

  • 来自your other question 我怀疑您现在已经切换到 Inkscape(它可以使用命令行上的选项缩放图像)。如果是这种情况,您会在问题下方添加评论吗? :).
  • 确实...我会相应地编辑我的问题。 :-)
  • 希望了解有关您的 Inkscape 解决方案的更多详细信息!
  • 对于仍有问题的任何人this 可能有用。我手动替换了
  • 使用库contao/imagine-svg,您可以在将 SVG 图像发送到 ImageMagick 之前调整其大小。

标签: php imagemagick imagick


【解决方案1】:

作为php_imagick's bug 的解决方法,您可以缩放 svg 的 width=".." 和 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;
}

然后你就可以轻松制作漂亮的透明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();
}

注意: 我一直在寻找如何从最初的小尺寸重新缩放 SVG 的解决方案。然而,imagick::setResolution 似乎被破坏了。但是,ImageMagick 库本身正在运行,因此您可以使用 exec('convert...') (出于安全原因,托管服务提供商可能会禁用)。

所以要从较小的 svg 创建 50x50 的缩略图,您可以这样做:

convert -density 500 -resize 50 50 -background transparent a.svg PNG32:a.png

【讨论】:

  • 宽度和高度必须以“px”为单位。
  • 另一个错误是图像不能很好地缩放,它们很模糊。解决方法是使用更大的宽度和高度(如果您使用不带像素的 svg viewBox="0 0 width height" 和带像素的 svg 宽度高度,则不会影响相对大小)。
【解决方案2】:

我一直在寻找解决方案,我在阅读这篇文章后找到了this,并且工作起来像个魅力:

$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;

感谢 php 手册页中该评论的作者。

【讨论】:

  • 这是迄今为止在转换为 PNG 之前升级 SVG 的最简单方法。比弄乱 DOMDocument 并尝试读取视图框要容易得多。我希望生成的 PNG 的宽度为 1600 像素,但要保持纵横比,但要处理具有许多不同视图框属性的 SVG。这就像一个魅力。
【解决方案3】:

您不需要 imagick 即可完成此任务。 例如,您不会调整 svg (w: 60px, h: 70px) => (w: 36px, h: 36px) 的大小来获取按钮的图标。

$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());
}

设置 translate(x,y) 时要小心,因为您的 svg 内容可能会设置在框外,除了背景之外您什么都看不到。

我上面的脚本只有在你的初始翻译设置为 (0,0) 时才能正常工作。你可以用这个

$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());

将 translate 设置为 (0,0) 并将路径数据调整为新设置,因为路径数据取决于 translate,反之亦然。

我使用这两个脚本来生成 png 图标,方法是将我的 svg 图标调整为我需要的尺寸并将它们转换为 png 而不会损失质量。

我希望我的意思很清楚。

【讨论】:

    【解决方案4】:

    这是一个示例,说明如何获取已经存在于字符串中的图像(例如,从数据库中),然后调整其大小、添加边框并打印出来。我用它来显示经销商徽标

      // 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;
    

    【讨论】:

    • 感谢chintu,不幸的是这对我没有帮助,因为我希望在光栅化之前调整SVG的大小以确保完整的质量。我当然可以简单地调整最终 jpeg 的大小而没有任何问题,但生成的图像会失去质量。首先调整 SVG 的大小(因为 SVG 是一种矢量格式),然后“然后”最后对图像进行光栅化,这将在升迁期间保持质量。如果我在最初的帖子中不够清楚,我很抱歉。谢谢你的尝试。 :-)
    【解决方案5】:

    为了缩放 SVG 文件中的宽度、高度、x、y 属性。

    function scale($svg, $k) {
        $rxvbox = '/viewBox="(\d+) (\d+) (\d+) (\d+)"/si';
        $rxw = '/width="(\d+)"/si';
        $rxh = '/height="(\d+)"/si';
        $rxx =  '/x="(\d+)"/si';
        $rxy =  '/y="(\d+)"/si';
        $svg = preg_replace_callback($rxvbox, function($m) use ($k) {return 'viewBox="'.$m[1]*$k.' '.$m[2]*$k.' '.$m[3]*$k.' '.$m[4]*$k.'"';}, $svg);
        $svg = preg_replace_callback($rxw, function($m) use ($k) {return 'width="'.$m[1]*$k.'"';}, $svg);
        $svg = preg_replace_callback($rxh, function($m) use ($k) {return 'height="'.$m[1]*$k.'"';}, $svg);
        $svg = preg_replace_callback($rxx, function($m) use ($k) {return 'x="'.$m[1]*$k.'"';}, $svg);
        $svg = preg_replace_callback($rxy, function($m) use ($k) {return 'y="'.$m[1]*$k.'"';}, $svg);
        return $svg;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-20
      • 2023-03-16
      • 2015-11-03
      • 1970-01-01
      • 2018-03-15
      • 2018-07-27
      • 2014-05-25
      • 1970-01-01
      相关资源
      最近更新 更多