2

I want to grab a SVG and save it as PNG (from a Chart plugin that creates SVG), and I thought I've figured out how to achieve this...

I've been looking around why onload functions are not firing, but still I can't understand why this code below does not work. Hello is not shown.

jQuery(function ($) {   

    var svg = $("#visualizer-152").find('svg');
    var imgsrc = 'data:image/svg+xml;base64,'+ btoa(svg);
    var canvas = document.createElement("canvas");
    var context = canvas.getContext("2d");
     
    drawing = new Image();
    //All above seems to work, no errors in console and objects
    //seems to be created correctly

    drawing.onload = function () {
        alert('hello');
        context.drawImage(drawing,0,0);
        var base64 = canvas.toDataURL('image/png', 1);
    };

    drawing.src = imgsrc;
});

UPDATE

Changed the onload to and it seemed to work...

$( drawing ).load( "", function() {
    context.drawImage(drawing,0,0);
    var base64 = canvas.toDataURL('image/png', 1);
    console.log('base64');
   console.log(base64);
});

But now another issue and I retrieve this error:

Uncaught InvalidStateError: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The HTMLImageElement provided is in the 'broken' state.

UPDATE2 If I try to click on the data string generated in the console:

var imgsrc = 'data:image/svg+xml;base64,'+ btoa(svg);
console.log(imgsrc);

I get this string in console:

data:image/svg+xml;base64,W29iamVjdCBPYmplY3Rd

and this error in the browser:

This page contains the following errors:

error on line 1 at column 1: Document is empty Below is a rendering of the page up to the first error.

UPDATE3: I'm beginning to realize the actual issue and it's the I can't grab the source correctly. I tested to set the source manually pointing to a specific file and then it worked. By looking at the generated svg content... (part of it)

<div id="visualizer-152">
<div style="position: relative;">
<div dir="ltr" style="position: relative; width: 660px; height: 400px;">
<div aria-label="Ett diagram." style="position: absolute; left: 0px; top: 0px; width: 100%; height: 100%;">
<svg width="660" height="400" aria-label="Ett diagram." style="overflow: hidden;">
<defs id="defs">
<clipPath id="_ABSTRACT_RENDERER_ID_0">
<rect x="125" y="77" width="411" height="247"></rect></clipPath>
</defs><rect x="0" y="0" width="660" height="400" stroke="none" stroke-  
width="0" fill="#ffffff"></rect>

and trying out an example that obviously does work: (taken from the question: How to draw an inline svg (in DOM) to a canvas?)

http://jsfiddle.net/epistemex/xfh7nctk/23/

In the jsfiddle, the source is grabbed by the outerHTML:

var svgText = document.getElementById("myViewer").outerHTML;

and the html from fiddle.. (start)

<div id="container" width=500px height=400px>
    <svg class="svg-editor" 
    id="myViewer" 
    width="1400" 
    height="1400" 
    xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" 
    style="overflow:scroll;margin:0;">

It doesn't work in my case though because the html doesn't look the same and svg does not have an id. I've tried to add an ID to the SVG, tried to add a class to the SVG but without success. I've also tried to create wrapper with wrap() - function in jQuery but also that without success:

isherwood
  • 58,414
  • 16
  • 114
  • 157
bestprogrammerintheworld
  • 5,417
  • 7
  • 43
  • 72
  • base64 - is not something, that browser needs to load. it is already loaded. thats why you have nothing fired. – primetwig Oct 14 '15 at 12:54
  • @primetwig - that does not make sense? I've updated the question. – bestprogrammerintheworld Oct 14 '15 at 12:59
  • you need to wait for svg to get loaded (if it is a file). as for now you are trying to create a base64 while source is not loaded yet. – primetwig Oct 14 '15 at 13:05
  • @primetwig The svg is loaded based jQuery(function ($) { ... because dom is loaded? Or do you mean I must do some onload svg function? The svg is created and I can access it through var svg = $("#visualizer-152").find('svg'); – bestprogrammerintheworld Oct 14 '15 at 13:08
  • If you open the result of `var imgsrc = 'data:image/svg+xml;base64,'+ btoa(svg);` in a new window, do you actually see the image? The rest of your code is fine, so I suspect the image data is broken. – 1cgonza Oct 14 '15 at 13:16
  • @1cgonza - please take a look at update2. Please tell me if I'm doing it incorrectly. – bestprogrammerintheworld Oct 14 '15 at 17:15

2 Answers2

1

Here is a variation of what you started in the fiddle, it seems to work for me. What is missing here is that you will have to play around with the canvas size to match your image, but you get the idea.

var container = document.getElementById('container');
var svgText   = document.getElementById("myViewer").outerHTML;
var myCanvas  = document.getElementById("canvas");
var ctxt      = myCanvas.getContext("2d");
var DOMURL    = window.URL || window.webkitURL || window;

var svg = new Blob([svgText], {type: 'image/svg+xml;charset=utf-8'});
var url = DOMURL.createObjectURL(svg);

var img = new Image();
    img.onload = createPNG;
    img.src = url;

function createPNG() {
  ctxt.drawImage(img, 0, 0);
  var canvasData = myCanvas.toDataURL('image/png');
  // For the download button you can use one you have on your HTML
  // or create one like this. Notice the download attribute where you can name your file.
  // https://developer.mozilla.org/en/docs/Web/HTML/Element/a#Using_the_download_attribute_to_save_a_canvas_as_a_PNG
  var saveBtn = document.createElement('a');
      saveBtn.href = canvasData;
      saveBtn.textContent = 'Download PNG';
      saveBtn.download = 'FILENAME.png';
  DOMURL.revokeObjectURL(url);
  container.appendChild(saveBtn);
}

EDIT: To select the svg without an ID, you can query any particular element using querySelector. Try replacing the first two lines with these:

var container = document.getElementById('visualizer-152');
var svgText = container.querySelector('svg').outerHTML;

The querySelector() will return only the first svg element it finds. If you have more than one SVG, you can use the querySelectorAll() which will return an array of all the svg elements.

1cgonza
  • 1,589
  • 10
  • 20
  • What is container and myViewer supposed to be then? (based on the html generated by the Visualizer Plugin that I've wrote about in update3) – bestprogrammerintheworld Oct 15 '15 at 05:20
  • The container is not going to affect the creation of the image (getting the SVG or generating the PNG), so it can be any element in your HTML where you would like to put the download button. It can be `var container = document.getElementById('visualizer-152');` or even just your body tag `var container = document.body;` – 1cgonza Oct 15 '15 at 05:46
  • I understand what you mean. I set the container as a wrapper to the visualizer-152 element. But the hard part is what to replace myViewer with? Because in the fiddle myViewer refers to the actual SVG, but in the generated html there are no id for the svg element. – bestprogrammerintheworld Oct 15 '15 at 05:59
  • See if my edit helps. In principle, it should not be hard to get an element in your HTML, especially now that we can use [querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector) natively in JS. – 1cgonza Oct 15 '15 at 06:18
  • I tried your edit and got this error when trying to get the outerHTML: Uncaught TypeError: Cannot read property 'outerHTML' of null. I also tried to var svgText = container.querySelector('svg')[0].outerHTML; and then got error: Uncaught TypeError: Cannot read property '0' of null – bestprogrammerintheworld Oct 15 '15 at 06:23
  • The SVG and all the html for the chart is only shown in the debugger, but when I look at "View source" I only see
    – bestprogrammerintheworld Oct 15 '15 at 06:25
  • When I explicty paste in the generated HTML into the page I manage to get the SVG-object. – bestprogrammerintheworld Oct 15 '15 at 06:30
  • Ok that is the problem then, the querySelector did not find any svg at the time it run. What are you using to generate the SVG? is it with a library like `d3`? or are you creating the svg somewhere else using JS? I believe you have to make sure this code runs only after the SVG is available in the DOM. – 1cgonza Oct 15 '15 at 06:33
  • "from a Chart plugin that creates svg" = Visualizer Chart Plugin ( https://sv.wordpress.org/plugins/visualizer/ ). How can I check that a svg is created then? – bestprogrammerintheworld Oct 15 '15 at 06:38
0

svg may contains &nbsp,cause image.onError

svg = svg.replace(/\&nbsp;/g, ' ');
user2955733
  • 385
  • 3
  • 4