将 SVG 转换为 PNG,并将应用的图像作为 svg 元素的背景

Posted

技术标签:

【中文标题】将 SVG 转换为 PNG,并将应用的图像作为 svg 元素的背景【英文标题】:Convert SVG to PNG with applied images as background to svg elements 【发布时间】:2016-03-06 16:54:09 【问题描述】:

我有一个外部 SVG 文件,其中包含一些嵌入的图像标签。每当我使用 toDataURL() 将此 SVG 转换为 PNG 时,生成的 PNG 图像不包含我作为模式应用于某些 SVG 路径的图像。有什么办法可以解决这个问题吗?

【问题讨论】:

我假设您正在使用这个htmlCanvasElement.toDataURL(),但为了帮助您调试问题,您应该粘贴用于画布和转换的代码。否则很难为您提供帮助... 【参考方案1】:

是的,有:将 svg 附加到您的文档中,并将所有包含的图像编码为 dataURI。

我正在写 a script 来执行此操作以及其他一些内容,例如包括外部样式表和其他一些 toDataURL 将失败的修复(例如,通过 xlink:href 属性或 <funciri> 引用的外部元素)。

这是我为解析图像内容编写的函数:

function parseImages()
    var xlinkNS = "http://www.w3.org/1999/xlink";
    var total, encoded;
    // convert an external bitmap image to a dataURL
    var toDataURL = function (image) 

        var img = new Image();
        // CORS workaround, this won't work in IE<11
        // If you are sure you don't need it, remove the next line and the double onerror handler
        // First try with crossorigin set, it should fire an error if not needed
        img.crossOrigin = 'Anonymous';

        img.onload = function () 
            // we should now be able to draw it without tainting the canvas
            var canvas = document.createElement('canvas');
            canvas.width = this.width;
            canvas.height = this.height;
            // draw the loaded image
            canvas.getContext('2d').drawImage(this, 0, 0);
            // set our <image>'s href attribute to the dataURL of our canvas
            image.setAttributeNS(xlinkNS, 'href', canvas.toDataURL());
            // that was the last one
            if (++encoded === total) exportDoc();
        ;

        // No CORS set in the response      
        img.onerror = function () 
            // save the src
            var oldSrc = this.src;
            // there is an other problem
            this.onerror = function () 
                console.warn('failed to load an image at : ', this.src);
                if (--total === encoded && encoded > 0) exportDoc();
            ;
            // remove the crossorigin attribute
            this.removeAttribute('crossorigin');
            // retry
            this.src = '';
            this.src = oldSrc;
        ;
        // load our external image into our img
        img.src = image.getAttributeNS(xlinkNS, 'href');
    ;

    // get an external svg doc to data String
    var parseFromUrl = function(url, element)
        var xhr = new XMLHttpRequest();
        xhr.onload = function()
            if(this.status === 200)
                var response = this.responseText || this.response;
                var dataUrl = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(response);
                element.setAttributeNS(xlinkNS, 'href', dataUrl);
                if(++encoded === total) exportDoc();
                
            // request failed with xhr, try as an <img>
            else
                toDataURL(element);
                
            ;
        xhr.onerror = function()toDataURL(element);;
        xhr.open('GET', url);
        xhr.send();
        ;

    var images = svg.querySelectorAll('image');
    total = images.length;
    encoded = 0;

    // loop through all our <images> elements
    for (var i = 0; i < images.length; i++) 
        var href = images[i].getAttributeNS(xlinkNS, 'href');
        // check if the image is external
        if (href.indexOf('data:image') < 0)
            // if it points to another svg element
            if(href.indexOf('.svg') > 0)
                parseFromUrl(href, images[i]);
                
            else // a pixel image
                toDataURL(images[i]);
            
        // else increment our counter
        else if (++encoded === total) exportDoc();
    
    // if there were no <image> element
    if (total === 0) exportDoc();

这里的 svgDoc 被称为 svg,exportDoc() 函数可以写成:

var exportDoc = function() 
    // check if our svgNode has width and height properties set to absolute values
    // otherwise, canvas won't be able to draw it
    var bbox = svg.getBoundingClientRect();

    if (svg.width.baseVal.unitType !== 1) svg.setAttribute('width', bbox.width);
    if (svg.height.baseVal.unitType !== 1) svg.setAttribute('height', bbox.height);

    // serialize our node
    var svgData = (new XMLSerializer()).serializeToString(svg);
    // remember to encode special chars
    var svgURL = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgData);

    var svgImg = new Image();

    svgImg.onload = function () 
        var canvas =  document.createElement('canvas');
        // IE11 doesn't set a width on svg images...
        canvas.width = this.width || bbox.width;
        canvas.height = this.height || bbox.height;

        canvas.getContext('2d').drawImage(svgImg, 0, 0, canvas.width, canvas.height);
        doSomethingWith(canvas)
    ;

    svgImg.src = svgURL;
;

但再一次,您必须先将您的 svg 附加到文档中(通过 xhr 或添加到 &lt;iframe&gt;&lt;object&gt; 元素中,并且您必须确保所有资源都是 CORS兼容(或来自同一域),以便获得这些渲染。

var svg = document.querySelector('svg');
var doSomethingWith = function(canvas) 
  document.body.appendChild(canvas)
;

function parseImages() 
  var xlinkNS = "http://www.w3.org/1999/xlink";
  var total, encoded;
  // convert an external bitmap image to a dataURL
  var toDataURL = function(image) 

    var img = new Image();
    // CORS workaround, this won't work in IE<11
    // If you are sure you don't need it, remove the next line and the double onerror handler
    // First try with crossorigin set, it should fire an error if not needed
    img.crossOrigin = 'anonymous';

    img.onload = function() 
      // we should now be able to draw it without tainting the canvas
      var canvas = document.createElement('canvas');
      canvas.width = this.width;
      canvas.height = this.height;
      // draw the loaded image
      canvas.getContext('2d').drawImage(this, 0, 0);
      // set our <image>'s href attribute to the dataURL of our canvas
      image.setAttributeNS(xlinkNS, 'href', canvas.toDataURL());
      // that was the last one
      if (++encoded === total) exportDoc();
    ;

    // No CORS set in the response		
    img.onerror = function() 
      // save the src
      var oldSrc = this.src;
      // there is an other problem
      this.onerror = function() 
        console.warn('failed to load an image at : ', this.src);
        if (--total === encoded && encoded > 0) exportDoc();
      ;
      // remove the crossorigin attribute
      this.removeAttribute('crossorigin');
      // retry
      this.src = '';
      this.src = oldSrc;
    ;
    // load our external image into our img
    var href = image.getAttributeNS(xlinkNS, 'href');
    // really weird bug that appeared since this answer was first posted
    // we need to force a no-cached request for the crossOrigin be applied
    img.src = href + (href.indexOf('?') > -1 ? + '&1': '?1');
  ;

  // get an external svg doc to data String
  var parseFromUrl = function(url, element) 
    var xhr = new XMLHttpRequest();
    xhr.onload = function() 
      if (this.status === 200) 
        var response = this.responseText || this.response;
        var dataUrl = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(response);
        element.setAttributeNS(xlinkNS, 'href', dataUrl);
        if (++encoded === total) exportDoc();
      
      // request failed with xhr, try as an <img>
      else 
        toDataURL(element);
      
    ;
    xhr.onerror = function() 
      toDataURL(element);
    ;
    xhr.open('GET', url);
    xhr.send();
  ;

  var images = svg.querySelectorAll('image');
  total = images.length;
  encoded = 0;

  // loop through all our <images> elements
  for (var i = 0; i < images.length; i++) 
    var href = images[i].getAttributeNS(xlinkNS, 'href');
    // check if the image is external
    if (href.indexOf('data:image') < 0) 
      // if it points to another svg element
      if (href.indexOf('.svg') > 0) 
        parseFromUrl(href, images[i]);
       else // a pixel image
        toDataURL(images[i]);
    
    // else increment our counter
    else if (++encoded === total) exportDoc();
  
  // if there were no <image> element
  if (total === 0) exportDoc();


var exportDoc = function() 
  // check if our svgNode has width and height properties set to absolute values
  // otherwise, canvas won't be able to draw it
  var bbox = svg.getBoundingClientRect();

  if (svg.width.baseVal.unitType !== 1) svg.setAttribute('width', bbox.width);
  if (svg.height.baseVal.unitType !== 1) svg.setAttribute('height', bbox.height);

  // serialize our node
  var svgData = (new XMLSerializer()).serializeToString(svg);
  // remember to encode special chars
  var svgURL = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgData);

  var svgImg = new Image();

  svgImg.onload = function() 
    var canvas = document.createElement('canvas');
    // IE11 doesn't set a width on svg images...
    canvas.width = this.width || bbox.width;
    canvas.height = this.height || bbox.height;

    canvas.getContext('2d').drawImage(svgImg, 0, 0, canvas.width, canvas.height);
    doSomethingWith(canvas)
  ;

  svgImg.src = svgURL;
;
window.onload = parseImages;
canvas 
  border: 1px solid green !important;
<svg   xmlns="http://www.w3.org/2000/svg" version="1.1">
  <defs>

    <pattern id="Pattern" x="0" y="0"  >
      <image xlink:href="https://dl.dropboxusercontent.com/s/1alt1303g9zpemd/UFBxY.png"  />
    </pattern>

  </defs>

  <rect fill="url(#Pattern)" x="0" y="0"  />
</svg>

【讨论】:

优秀的解决方案。 +1 虽然我会说最好在 ifelse 等之后的单个语句中包含 ,因为这是新手最容易被忽视的错误来源之一。 @Blindman67 你是对的,我的坏习惯,快速从整个脚本中复制粘贴,仍然需要粗略的清理...... @Kaiido 我遇到了类似的问题link 通过不同的类更改我的内容的背景图像后,它不显示图像,只是一个空格:) @Kaiido,你的代码很棒,但我认为它会受益于一些异步方法,对吧?

以上是关于将 SVG 转换为 PNG,并将应用的图像作为 svg 元素的背景的主要内容,如果未能解决你的问题,请参考以下文章

如何将 PNG 图像转换为 SVG? [关闭]

回形针 - 将 SVG 转换为 PNG 时保持透明度

使用 PHP 将 SVG 图像转换为 PNG

在浏览器中将 SVG 转换为图像(JPEG、PNG 等)

在不更改字体的情况下将SVG转换为PNG

如何将 SVG 文件转换为 JPEG?