pxthxk
pxthxk

Reputation: 4385

Image cropped while converting SVG to PNG

While saving an SVG to PNG, the image saved contains only the SVG rendered in the viewbox/window. How can one save a large PNG, containing the whole SVG?

// SVG element and XML string.
var svg = document.querySelector('svg');
var svgData = new XMLSerializer().serializeToString(svg);

// Canvas to hold the image.
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');

// Canvas size = SVG size.
var svgSize = svg.getBoundingClientRect();
canvas.width = svgSize.width;
canvas.height = svgSize.height;

// Image element appended with data.
var img = document.createElement('img');
img.setAttribute('src', 'data:image/svg+xml;base64,' + btoa(svgData));

img.onload = function() {
    // Draw image on canvas and convert to URL.
    context.drawImage(img,0,0);
    console.log(canvas.toDataURL('image/png'));
};

Upvotes: 1

Views: 1159

Answers (2)

Kaiido
Kaiido

Reputation: 136698

This is because you are setting your canvas size to your rendered svg ones. In your CSS, you probably do resize your svg, which results in a difference between it's computed size and it's natural one.
By default, drawImage(img, dx, dy, dWidth, dHeight) will use the source's width and height as destinationWidth and destinationHeight if these parameters are not passed.

You can check this example showing the same behavior with a raster image :

window.onload = function(){

var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');

var svgSize = inDoc.getBoundingClientRect();
canvas.width = svgSize.width;
canvas.height = svgSize.height;

var img = document.createElement('img');
img.setAttribute('src',  inDoc.src);

img.onload = function() {
  context.drawImage(img,0,0);
  document.body.appendChild(canvas);
};
}
img{ width: 64px; height: 64px}
<img id="inDoc" src="http://lorempixel.com/128/128"/>

And here is a little graphic showing what's happening.

explanation of the SVGElement size vs img.svg size

So the solution is just to set your canvas' width and height properties to your img's ones, just like you would do with any other source :

img.onload = function(){
    canvas.width = this.width;
    canvas.height = this.height;
    ctx.drawImage(this, 0,0);
    }

and if you want to include some scaling factor :

img.onload = function(){
    canvas.width = this.width * scale;
    canvas.height = this.height * scale;
    ctx.drawImage(this, 0,0, canvas.width, canvas.height);
    }

Now, one not related to your actual code but still huge difference between raster images and svg images is that svg width and height can be set to relative units (like %).
Browsers have no direct clue about what it's relative to. (Chrome does a guess, I don't know how, others won't render your image).

So you need to check for this before exporting to a dataURI :

var absoluteUnits = [1,5,6,7,8,9,10];
if (absoluteUnits.indexOf(svg.width.baseVal.unitType)<0) {
    svg.setAttribute('width', aboluteWidth);
    }
if (absoluteUnits.indexOf(svg.height.baseVal.unitType)<0) {
    svg.setAttribute('height', aboluteHeight);
    }

Here absoluteWidth and absoluteHeight can be the results of svg.getBoundingClientRect().

Also note that IE9 won't be able to show the img.width and img.height values, so you've got to make a special case for it... ... but since you should have already checked for the absoluteSize, this should not be a problem :

var svg = document.querySelector('svg');
// we'll use a copy to not modify the svg in the document
var copy = svg.cloneNode(true);
var absoluteWidth, absoluteHeight, BBox;
var absoluteUnits = [1,5,6,7,8,9,10];

if (absoluteUnits.indexOf(svg.width.baseVal.unitType)<0) {
  BBox = svg.getBoundingClientRect();
  absoluteWidth = BBox.width
  copy.setAttribute('width', absoluteWidth);
  }
else{
  absoluteWidth = svg.getAttribute('width');
  }
if (absoluteUnits.indexOf(svg.height.baseVal.unitType)<0) {
  if(!BBox){
     BBox = svg.getBoundingClientRect();
    }
  absoluteHeight = BBox.height;
  copy.setAttribute('height', absoluteHeight)
  }
else{
  absoluteHeight = svg.getAttribute('height');
  }

var svgData = new XMLSerializer().serializeToString(copy);

var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');

var img = document.createElement('img');
img.setAttribute('src', 'data:image/svg+xml;base64,' + btoa(svgData));

img.onload = function() {
    // here you set your canvas width and height
    canvas.width = this.width || absoluteWidth;
    canvas.height = this.height || absoluteHeight;

    context.drawImage(img,0,0);
    document.body.appendChild(canvas);
};
svg{width: 64px; height:64px; border: 1px solid green;}
canvas{border: 1px solid blue;}
<svg width="128px" height="128px" viewBox="0 0 128 128">
  <rect x="20" y="20" width="84" height="84"/>
</svg>

Upvotes: 2

zer00ne
zer00ne

Reputation: 43880

Instead of:

var svgSize = svg.getBoundingClientRect();

Use:

var svgSize = svg.viewBox.baseVal;

This will get you the true dimensions of the viewBox.

REFERENCE

https://stackoverflow.com/a/7682976/2813224

SNIPPET

// SVG element and XML string.
var svg = document.querySelector('svg');
var svgData = new XMLSerializer().serializeToString(svg);

// Canvas to hold the image.
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');

// Canvas size = SVG size.
var svgSize = svg.viewBox.baseVal;
canvas.width = svgSize.width;
canvas.height = svgSize.height;

// Image element appended with data.
var img = document.createElement('img');
img.setAttribute('src', 'data:image/svg+xml;base64,' + btoa(svgData));

img.onload = function() {
    // Draw image on canvas and convert to URL.
    context.drawImage(img,0,0);
    console.log(canvas.toDataURL('image/png'));
};
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" width="64px" height="64px" viewBox="-0.5 0.5 64 64" enable-background="new -0.5 0.5 64 64" xml:space="preserve">
<g>
	<circle fill="#FFFFFF" cx="31.325" cy="32.873" r="30.096"/>
	<path id="text2809_1_" d="M31.5,14.08c-10.565,0-13.222,9.969-13.222,18.42c0,8.452,2.656,18.42,13.222,18.42   c10.564,0,13.221-9.968,13.221-18.42C44.721,24.049,42.064,14.08,31.5,14.08z M31.5,21.026c0.429,0,0.82,0.066,1.188,0.157   c0.761,0.656,1.133,1.561,0.403,2.823l-7.036,12.93c-0.216-1.636-0.247-3.24-0.247-4.437C25.808,28.777,26.066,21.026,31.5,21.026z    M36.766,26.987c0.373,1.984,0.426,4.056,0.426,5.513c0,3.723-0.258,11.475-5.69,11.475c-0.428,0-0.822-0.045-1.188-0.136   c-0.07-0.021-0.134-0.043-0.202-0.067c-0.112-0.032-0.23-0.068-0.336-0.11c-1.21-0.515-1.972-1.446-0.874-3.093L36.766,26.987z"/>
	<path id="path2815_1_" d="M31.433,0.5c-8.877,0-16.359,3.09-22.454,9.3c-3.087,3.087-5.443,6.607-7.082,10.532   C0.297,24.219-0.5,28.271-0.5,32.5c0,4.268,0.797,8.32,2.397,12.168c1.6,3.85,3.921,7.312,6.969,10.396   c3.085,3.049,6.549,5.399,10.398,7.037c3.886,1.602,7.939,2.398,12.169,2.398c4.229,0,8.34-0.826,12.303-2.465   c3.962-1.639,7.496-3.994,10.621-7.081c3.011-2.933,5.289-6.297,6.812-10.106C62.73,41,63.5,36.883,63.5,32.5   c0-4.343-0.77-8.454-2.33-12.303c-1.562-3.885-3.848-7.32-6.857-10.33C48.025,3.619,40.385,0.5,31.433,0.5z M31.567,6.259   c7.238,0,13.412,2.566,18.554,7.709c2.477,2.477,4.375,5.31,5.67,8.471c1.296,3.162,1.949,6.518,1.949,10.061   c0,7.354-2.516,13.454-7.506,18.33c-2.592,2.516-5.502,4.447-8.74,5.781c-3.2,1.334-6.498,1.994-9.927,1.994   c-3.468,0-6.788-0.653-9.949-1.948c-3.163-1.334-6.001-3.238-8.516-5.716c-2.515-2.514-4.455-5.353-5.826-8.516   c-1.333-3.199-2.017-6.498-2.017-9.927c0-3.467,0.684-6.787,2.017-9.949c1.371-3.2,3.312-6.074,5.826-8.628   C18.092,8.818,24.252,6.259,31.567,6.259z"/>
</g>
</svg>

Upvotes: 2

Related Questions