13

I'm trying to convert a large SVG (it's data URL is about 750000 - 1000000 characters) to a PNG by passing it's data url through an image and into a canvas but the image is only loading about 1/4 of the SVG.

Creating via:

var svg_xml = (new XMLSerializer()).serializeToString(svg),
    url = 'data:image/svg+xml;base64,' + btoa(svg_xml);

var img = new Image();
img.width = 730;
img.height = 300;
img.onload = function(){
    var canvas = document.create('canvas');
    canvas.width = 730;
    canvas.height = 300;

    var ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0, 730, 300);

    callbackFn(canvas.toDataURL('image/png');
}
img.src = url

Edit

I've tried implementing canvg to draw the SVG but the DataURL produced results in a blank image:

var svg_xml = (new XMLSerializer()).serializeToString(svg);
var canvas = document.createElement('canvas');
canvas.width = 730;
canvas.height = 300;

var ctx = canvas.getContext('2d');
ctx.drawSvg(svg_xml, 0, 0, 730, 300);
callbackFn(canvas.toDataURL('image/png');

Is there anything wrong with the method I've used?

Further Edit

I'm now fairly convinced that it's the canvas failing to draw the whole image as I tried implementing a Blob solution to the same effect:

var svg_xml = (new XMLSerializer()).serializeToString(svg),
    blob = new Blob([svg_xml], {type:'image/svg+xml;charset=utf-8'}),
    url = window.URL.createObjectURL(blob);

var img = new Image();
img.width = 730;
img.height = 300;
img.onload = function(){
    var canvas = document.create('canvas');
    canvas.width = 730;
    canvas.height = 300;

    var ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0, 730, 300);

    window.URL.revokeObjectURL(url);
    callbackFn(canvas.toDataURL('image/png');
}
img.src = url   

The image again loads fine and going to the URL (before it's revoked) displays the image fine as well.

The length of the canvas dataURL is not consistent so I don't think that's maxing out, is there a way of detecting the canvas size? The application is only supported for Chrome and FireFox.

Mikko Ohtamaa
  • 82,057
  • 50
  • 264
  • 435
matts1189
  • 187
  • 1
  • 3
  • 12
  • Very interesting, it all looks like it should work... Not sure how well SVGs obey width and height rules (I tend to have some CSS issues with sizing them) but this should work. Irrelevant, but: shouldn't you _call_ the image constructor (aka `new Image()` instead of `new Image`)? – somethinghere Aug 26 '15 at 15:44
  • That was a miss-type when writing this out... – matts1189 Aug 26 '15 at 15:54
  • The SVGs are graphs drawn through D3 with large amounts of data – matts1189 Aug 26 '15 at 15:56
  • Check out this answer here: http://stackoverflow.com/questions/3768565/drawing-a-svg-file-on-a-html5-canvas - I dont think I can do better :) – somethinghere Aug 26 '15 at 15:59
  • You don't need to `btoa` it, just use this src : `"data:image/svg+xml; charset=utf8," + encodeURIComponent(svg_xml);` You will save a few bytes, but maybe not enough to reach [this limit](http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers) which may be the cause of your problem. – Kaiido Aug 27 '15 at 02:46
  • Using encodedURIComponent seemed to make no difference. I've been looking at the image and that seems to load the SVG fine, it seems that it's the canvas that isn't drawing everything. – matts1189 Aug 27 '15 at 08:42
  • Is it possible that you're using some `` elements with external src in your svg? Anyway, at this point I'd really like to see a live fiddle of this problem – Kaiido Aug 27 '15 at 09:24
  • The code lives on a secure network with no external connection so I cannot provide a fiddle. The SVG is a scatter plot created via D3.js so has no images inside. – matts1189 Aug 27 '15 at 09:45
  • Hmm and I guess that a usb-key to save the svg datas before uploading it to a pastebin is a no-go too... Too bad, that sounds really interesting but without the actual svg, hard to say what it is... The only typo I can see in your given code is `document.create('canvas');` but I guess it's not in the actual code. – Kaiido Aug 27 '15 at 10:28
  • possible duplicate of [Convert SVG to image (JPEG, PNG, etc.) in the browser](http://stackoverflow.com/questions/3975499/convert-svg-to-image-jpeg-png-etc-in-the-browser) – Mr. Polywhirl Aug 27 '15 at 17:04
  • @Mr.Polywhirl it's not a duplicate since OP knows how to do it but encounters an unknown bug while doing it. He's not asking how to do x but why it doesn't work. – Kaiido Aug 28 '15 at 02:35
  • Once again, the only error in your code is `document.create('canvas')` which should be `document.createElement('canvas')`. Now, a way to get canvas width/height is to check `canvas.width` & `canvas.height` but since you hardcoded them, there is no reason the problem occurs. An other debug step you could attempt would be to append your canvas in the document, you'd be able to see it live. And once again, the only way for us to help you, would be that you send us an example svg data, so we can repro the issue. – Kaiido Sep 02 '15 at 10:47
  • The SVGs contain confidential data so I cannot provide them. In the actual code, createElement is used (surely an exception would be thrown if it was like that). I think part of my problem is that the svg_xml has too much style. To maintain styles taken from CSS files, I've looped through all the nodes, and used getComputedStyle to set the style attribute on the node. Checking through the result, there is LOTS of CSS. Is there a better way of getting the CSS? – matts1189 Sep 02 '15 at 10:57
  • Are the CSS incorporated in the svg doc or in the html one? For the former, canvas should be able to apply them, for the later, then try to make it the former way :-) – Kaiido Sep 02 '15 at 12:56
  • 2
    @somethinghere `new Image` and `new Image()` are both both valid, you can eliminate the parenthesis if you aren't passing any parameters – Jim Jones Oct 20 '15 at 20:13
  • 1
    @spencerkillen Cool, i never thought to ommit the parenthesis. Although both valid, my gut feeling likes the parenthesis as they look like something is executed, not declared. But good to know, thanks! – somethinghere Oct 20 '15 at 20:16
  • 1
    @matts1189 What is the width/height setting in the SVG XML? i.e in this node: `` – Don Rhummy Oct 22 '15 at 01:18
  • Also, for your CSS, try to append the full [CSS stylesheet into your svg document](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/style) instead of applying each rules to each node inline. – Kaiido Oct 22 '15 at 01:33
  • [Please check my fiddle](http://jsfiddle.net/vyLtxgh4/1/), It's working. To help more I need your SVGs. [For detail explore this tutorial](http://it-supernova.com/convert-svg-to-png-using-canvas/) – Khalid Hussain Oct 22 '15 at 03:57

1 Answers1

3

Working fiddle.

HTML:

    <div>
        <svg xmlns="http://www.w3.org/2000/svg"
              width="526" height="233">
          <rect x="13" y="14" width="500" height="200" rx="50" ry="100"
              fill="none" stroke="blue" stroke-width="10" />
        </svg>
        <a id='imgId'>Save</a>
    </div>
    <canvas></canvas>

JavaScript:

var svg = document.getElementsByTagName('svg')[0];
var svg_xml = (new XMLSerializer()).serializeToString(svg),
    blob = new Blob([svg_xml], {type:'image/svg+xml;charset=utf-8'}),
    url = window.URL.createObjectURL(blob);

var img = new Image();
img.width = 730;
img.height = 300;
img.onload = function(){
    var canvas = document.createElement('canvas');
    canvas.width = 730;
    canvas.height = 300;

    var ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0, 730, 300);

    window.URL.revokeObjectURL(url);
    var canvasdata = canvas.toDataURL('image/png');
    var a = document.getElementById('imgId');
    a.download = "export_" + Date.now() + ".png";
    a.href=canvasdata;   
}
img.src = url

Your question:

Why image is only loading about 1/4 of the SVG?

Answer:

Just follow these rules:

  1. x of <rect/> + width of <rect/> <= width of <svg/>
  2. y of <rect/> + height of <rect/> <= height of <svg/>

For example:

<svg xmlns="http://www.w3.org/2000/svg" width="500" height="200">
     <rect x="10" y="10" width="550" height="250" rx="50" ry="100"
                  fill="none" stroke="blue" stroke-width="10" />
</svg>

Here, x of <rect/> = 10, width of <rect/> = 550, width of <svg> = 500

so, 10+550>500

In this case image will be partially rendered.

Hope this will solve your problem.

Khalid Hussain
  • 1,675
  • 17
  • 25
  • But in that case, wouldn't the rendered original svg also be cropped? – Kaiido Oct 22 '15 at 05:10
  • Check my fiddle and change the value of `x` and `width`..... It will help you to visualize the result. – Khalid Hussain Oct 22 '15 at 05:16
  • If that comment was to my attention, please note I'm not the OP. I don't have access either to the criminal svg file, and I asked for it 2 months ago. Now for the comment on your answer, I made it a question, but it was actually a rhetoric one. I know that if your assumption was the right one, the image would not "loads fine and going to the URL (before it's revoked) displays the image fine as well." It will be cropped as it is when drawn on canvas. [small example using your fiddle](http://jsfiddle.net/vyLtxgh4/3/) – Kaiido Oct 22 '15 at 05:26