Canvas.toDataURL() not showing background image of

2019-09-09 08:34发布

I have added background image to canvas using css.

But when converting it into png using canvas.toDataURL() , its not showing background image.

It seems like its not parsing css background image.

var svgString = new XMLSerializer().serializeToString(document.querySelector('svg'));

        var canvas = document.getElementById("canvas");
        d3.select('canvas')
          .attr('style', 'background-image:url("chart.png");background-position:50%;background-repeat:no-repeat');

        var ctx = canvas.getContext("2d");

        var DOMURL = self.URL || self.webkitURL || self;
        var img = new Image();
        //var svg = new Blob([svgString], {type: "image/svg+xml;charset=utf-8"});
        var svg = new Blob([svgString], {type: "image/svg+xml"});
        var url = DOMURL.createObjectURL(svg);


        var png = '';



        img.onload = function() {


           ctx.drawImage(img, 0, 0);
            png = canvas.toDataURL("image/png");
           document.querySelector('#chart').innerHTML = '<img src="'+png+'"/>';
            DOMURL.revokeObjectURL(png);
            img.src = "";
        };
        img.crossOrigin = "anonymous";


        img.src = url;

2条回答
迷人小祖宗
2楼-- · 2019-09-09 09:09

As said in comments, background-image is not part of the canvas context and thus, can't be exported by any canvas' export methods.

If you want to get this image drawn on the canvas, simply draw it before you draw the other image.* The main difficulty being to reproduce the different option CSS background has. But in your case, it's quite simple.

var svgString = new XMLSerializer().serializeToString(document.querySelector('svg'));

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

var DOMURL = self.URL || self.webkitURL || self;

var svg = new Blob([svgString], {
  type: "image/svg+xml"
});
var url = DOMURL.createObjectURL(svg);
var png = '';

var toLoad = 2;
var loaded = 0;
var background = new Image();
var svgImg = new Image();

// attach the event to both images
background.onload = svgImg.onload = function(){
 // only when both has loaded
 if(++loaded === toLoad){
  
  // set the canvas size to the svg image's one
  canvas.width = svgImg.width;
  canvas.height = svgImg.height;
  
  // draw the background image first
  ctx.drawImage(background, (canvas.width/2)-(background.width/2), (canvas.height/2)-(background.height/2));
	// then the svg image
  ctx.drawImage(svgImg, 0, 0);
  png = canvas.toDataURL("image/png");
  document.querySelector('#chart').innerHTML = '<img src="' + png + '"/>';
  // you want to revoke the svgImg ObjectURL, not the canvas dataURI
  DOMURL.revokeObjectURL(svgImg.src);
 
 }
}
svgImg.src = url;
// set it only if you're doing a cross origin request
// Object URL are not
background.crossOrigin = 'anonymous';
background.src = 'https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png'
svg{border: 1px solid red;}
canvas{border: 1px solid blue;}
img{border: 1px solid green;}
<svg width="120" height="120" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
  <rect x="10" y="10" width="55" height="55" fill-opacity=".5"/>
</svg>

<canvas id="canvas"></canvas>

<p id="chart"></p>

*As said by markE, if you can't draw the background before, you can also set the globalCompositeOperation of your context to destination-over in order to draw behind the actual content.


Now, this wasn't clearly part of your question, but if you tried to set the background-image to your svg node, you should have been able to draw it on your canvas image, as part of the svg.

But since HTMLImage (<img>) element can't load any external resources form its loaded media, you would have to first convert the image to a dataURI version, and append the styles either as the svg's style attribute, either in a <style> element appended inside the svg element.

Here is a ugly example that will convert an svg element with a CSS defined background-image, and draw it on the canvas.
Don't use it, this just here for the demo but is likely to break. Also, this has only been quickly tested against FF, Chrome and Safari.

function convertSVGWithBG(svg, callback) {

  // this function is not be bullet proof, better if you know the urls in advance

  // in case of multi-images
  var imgsToLoad = getComputedStyle(svg).backgroundImage.split(',');

  appendDocumentStylesTo(svg);

  var toLoad = imgsToLoad.length,
    loaded = 0,
    // we need to keep it sorted
    rules = [],
    canvas = document.createElement('canvas'),
    ctx = canvas.getContext('2d');

  imgsToLoad.forEach(function(s, i) {

    var bgURL = parseURL(s),
      bgImg = new Image();
    bgImg.crossOrigin = 'anonymous';

    var errored = false;

    bgImg.onload = function() {
      canvas.width = this.width;
      canvas.height = this.height;
      ctx.drawImage(this, 0, 0);
      rules[i] = canvas.toDataURL();
      if (++loaded === toLoad) {
        onend();
      }
      bgImg.onerror = function() {

        if (!errored) {
          errored = true;
          this.crossOrigin = null;
          this.removeAttribute('crossorigin');
          this.src = this.src;
        } else {
          console.warn('failed to load img at src ', this.src);
          if (--toLoad === loaded) {
            onend();
          }
        }
      };
    };

    bgImg.src = bgURL;

    function onend() {
      var toLoad = rules.filter(function(r) {
          return r;
        }).length,
        loaded = 0;
      // wait for the dataURI version has loaded too
      rules.forEach(function(url) {
        var img = new Image();
        img.onload = function() {
          if (++loaded === toLoad) {

            callback(svg);

          }
        };
        img.src = url
      });
      // it has to be inline style, or appended in a <style> element directly in our svg element
      var fullRule = 'url(' + rules.join('), url(') + ')';
      svg.style.backgroundImage = fullRule;
    }

  });

  function parseURL(str) {
    // this is ugly and there should be a better way to do so (maybe regex)
    var url = str.split('"')[1];
    if (!url) {
      url = str.split("'")[1];
    }
    if (!url) {
      url = str.split('(')[1].split(')')[0];
    }
    return url;
  }
}

function svgToCanvas(svg) {

  var svgString = new XMLSerializer().serializeToString(svg);
  var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");

  var DOMURL = self.URL || self.webkitURL || self;

  var svg = new Blob([svgString], {
    type: "image/svg+xml"
  });
  var url = DOMURL.createObjectURL(svg);

  var svgImg = new Image();
  svgImg.onload = function() {
    canvas.width = svgImg.width;
    canvas.height = svgImg.height;
    ctx.drawImage(svgImg, 0, 0);
    png = canvas.toDataURL("image/png");
    document.querySelector('#chart').innerHTML = '<img src="' + png + '"/>';
    DOMURL.revokeObjectURL(svgImg.src);
  }
  svgImg.src = url;

}

var svg = document.querySelector('svg');
convertSVGWithBG(svg, svgToCanvas);

// this is completely unstable and should never be used...
function appendDocumentStylesTo(element) {

  var docStyleSheets = document.styleSheets;
  var i = 0,
    j, rules, r, key;
  for (i; i < docStyleSheets.length; i++) {
    rules = docStyleSheets[i].cssRules;
    for (j = 0; j < rules.length; j++) {
      r = rules[j];
      if (element.matches(r.selectorText)) {

        if (r.style) {
          for (k = 0; k < r.style.length; k++) {
            key = r.style[k];
            // ugly hack for Safari
            if (key.indexOf('repeat') > -1) {
              key = 'background-repeat';
            }
            element.style[key] = r.style[key];
          }
        }
      }
    }
  }

}
canvas {
  border: 1px solid blue;
}
img {
  border: 1px solid green;
}
svg {
  background-image: url(https://dl.dropboxusercontent.com/s/rumlhyme6s5f8pt/ABC.png), url(https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png);
  background-position: 50%;
  background-repeat: no-repeat;
}
<svg width="120" height="120" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
  <rect x="10" y="10" width="55" height="55" fill-opacity=".5" />
</svg>

<canvas id="canvas"></canvas>

<p id="chart"></p>

查看更多
Animai°情兽
3楼-- · 2019-09-09 09:22
This solution is working 

var svgString = new XMLSerializer().serializeToString(document.querySelector('svg'));

        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");
        var DOMURL = self.URL || self.webkitURL || self;
        var img = new Image();
        //var svg = new Blob([svgString], {type: "image/svg+xml;charset=utf-8"});
        var svg = new Blob([svgString], {type: "image/svg+xml"});
        var url = DOMURL.createObjectURL(svg);

        var png = '';
        var emailConfirmationMessage = '';
        var img1 = new Image();

        img.onload = function() {
            ctx.drawImage(img1, 136, 136);
            ctx.drawImage(img, 0, 0);

            sendEmail(canvas,DOMURL);

        };

         img1.onload = function(){
           img.src = url;
         };
        img1.src = 'chart1.png';
查看更多
登录 后发表回答