3

How can I get base64 with image inside of svg? Check this Fiddle that I got from another question. If you see the second graphic, its not generating the image that overlays the bar.

var chart = new google.visualization.ColumnChart(document.getElementById('chart_survey'));

$("[fill='#FFFFFF']").each(function( index, element ) {
    var svgimg = document.createElementNS('http://www.w3.org/2000/svg','image');
    svgimg.setAttributeNS(null,'x',element.x.baseVal.value);
    svgimg.setAttributeNS(null,'y',element.y.baseVal.value);
    svgimg.setAttributeNS(null,'width',element.width.baseVal.value);
    svgimg.setAttributeNS(null,'height',element.height.baseVal.value);
    svgimg.setAttributeNS(null,'preserveAspectRatio','none');
    svgimg.setAttributeNS('http://www.w3.org/1999/xlink','href', '${application.contextPath}/images/textura/patt.gif');
    $(element).parent().append(svgimg);
});

$('#test').val(chart.getImageURI())
fsi
  • 1,319
  • 1
  • 23
  • 51
  • http://fc03.deviantart.net/fs27/i/2011/193/9/f/texture_1a_by_jakezdaniel-d1b6lmo.jpg appear to be image at bars at top chart ? Is requirement to apply same image to lower chart ? Not appear to be attempt to retrieve `base64` at jsfiddle http://jsfiddle.net/R8A8P/51/ ? – guest271314 May 08 '15 at 21:46
  • Yes, the image is appeared to be top chart. and Yes, its requirement, because instead of colors, I want images, so I put in at the top. But when I retrieve base64, the image is not there. – fsi May 08 '15 at 22:56
  • Cannot utilize `image` that already populates bars ? What would be difference between `base64` and existing image link ? – guest271314 May 08 '15 at 23:15
  • I want to send to the server and put it into a pdf. – fsi May 08 '15 at 23:23
  • And I dont know what you mean in your question – fsi May 08 '15 at 23:32
  • 1
    See post. After re-viewing Question , believe that requirement is to create `png` image from `svg` element ? Tried , though did not retain linked image for bars colors . Created `data URI` of `svg` which retained linked image at bars when loaded in in browser. – guest271314 May 09 '15 at 01:31
  • 1
    See http://php.net/imagick – guest271314 May 10 '15 at 00:02
  • See also https://bugzilla.mozilla.org/show_bug.cgi?id=672013 , https://bugs.webkit.org/show_bug.cgi?id=129172 – guest271314 May 10 '15 at 17:13

4 Answers4

2

In order to save this svg to a png, which keeps the linked <image> then you'll have to encode each <image>'s href to a dataURL first.

Edit

I rewrote the original snippets (which can still be found in the edit history).

  • I changed the <image> tags to <use>. This results in a much smaller memory usage.

  • I also added a fallback for IE11, which accepts to encode the external image to data URL, but still fails to convert svg to png via canvas. The fallback will replace the destination <img>tag, with the canvas. The later can be saved by user with a right click.

  • A few caveats :
    It doesn't work in Safari 7, and maybe in other outdated webkit browsers. That's a strange bug, since it does work like a charm in localhost, but won't on any other network (even on my home network, using 192.168.xxx).
    IE 9 & IE 10 will fail to convert the external images to data URL, CORS problem.

// What to do with the result (either data URL or directly the canvas if tainted)
var callback = function(d, isTainted) {
  if (!isTainted) {
    $('#chartImg')[0].src = d;
  } else
    $('#chartImg')[0].parentNode.replaceChild(d, $('#chartImg')[0]);
};
// The url of the external image (must be cross-origin compliant)
var extURL = 'https://dl.dropboxusercontent.com/s/13dv8vzmrlcmla2/tex2.jpg';

google.load('visualization', '1', {
  packages: ['corechart']
})

var encodeCall = getbase64URI.bind(this, extURL, callback);
google.setOnLoadCallback(encodeCall);



// Google Chart part
function drawVisualizationDaily(imgUrl, callback, isTainted) {

  var data = google.visualization.arrayToDataTable([
    ['Daily', 'Sales'],
    ['Mon', 4],
    ['Tue', 6],
    ['Wed', 6],
    ['Thu', 5],
    ['Fri', 3],
    ['Sat', 7],
    ['Sun', 7]
  ]);

  var chart = new google.visualization.ColumnChart(document.getElementById('visualization'));

  chart.draw(data, {
    title: "Daily Sales",
    width: 500,
    height: 400,
    hAxis: {
      title: "Daily"
    }
  });

  // Link to chart's svg element
  var svgNode = chart.ea.querySelector('svg');
  // Create a symbol for our image
  var symbol = document.createElementNS('http://www.w3.org/2000/svg', 'symbol');
  // An svg wrapper to allow size changing with <use>
  symbol.setAttributeNS(null, 'viewBox', '0,0,10,10');
  symbol.setAttributeNS(null, 'preserveAspectRatio', 'none');
  symbol.id = 'background';
  // And the actual image, with our encoded image
  var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
  img.setAttributeNS(null, 'preserveAspectRatio', 'none');
  img.setAttributeNS(null, 'width', '100%');
  img.setAttributeNS(null, 'height', '100%');
  img.setAttributeNS('http://www.w3.org/1999/xlink', 'href', imgUrl);

  symbol.appendChild(img);
  svgNode.appendChild(symbol);

  var blueRects = $("[fill='#3366cc']");
  var max = blueRects.length - 1;
  blueRects.each(function(index, element) {
    var svgimg = document.createElementNS('http://www.w3.org/2000/svg', 'use');
    svgimg.setAttributeNS(null, 'x', element.x.baseVal.value);
    svgimg.setAttributeNS(null, 'y', element.y.baseVal.value);
    svgimg.setAttributeNS(null, 'width', element.width.baseVal.value);
    svgimg.setAttributeNS(null, 'height', element.height.baseVal.value);
    svgimg.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#background');
    svgNode.appendChild(svgimg);

    if (index === max && !isTainted) // no need to call it if we don't have our dataURL encoded images
    // a load event would be better but it doesn't fire in IE ...
      setTimeout(exportSVG.bind(this, svgNode, callback, isTainted), 200);
  });
}

function exportSVG(svgNode, callback, isTainted) {

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

  var img = new Image();
  img.onload = function() {
    var canvas = document.createElement('canvas');
    canvas.width = svgNode.getAttribute('width');
    canvas.height = svgNode.getAttribute('height');
    canvas.getContext('2d').drawImage(this, 0, 0);
    var data, isTainted;
    try {
      data = canvas.toDataURL();
    } catch (e) {
      data = canvas;
      isTainted = true;
    }
    callback(data, isTainted);
  }
  img.src = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgData);
}

// A simple function to convert an images's url to base64 data URL
function getbase64URI(url, callback) {
  var img = new Image();
  img.crossOrigin = "Anonymous";
  img.onload = function() {
    var c = document.createElement('canvas');
    c.width = this.width;
    c.height = this.height;
    c.getContext('2d').drawImage(this, 0, 0);
    var isTainted;
    try {
      c.toDataURL();
    } catch (e) {
      isTainted = true;
    }
    // if the canvas is tainted, return the url
    var output = (isTainted) ? url : c.toDataURL();
    drawVisualizationDaily(output, callback, isTainted);
  }
  img.src = url;
}
svg    { border: 1px solid yellow; }
img    { border: 1px solid green;  }
canvas { border: 1px solid red;    }
<script src="http://www.google.com/jsapi?.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="visualization"></div>
Right-click this image to save it:
<br>
<img id="chartImg" />
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Be careful however, this can be really consumptive : it first gets all the images from the server and then encode them before reencoding the final image to a last dataURL. For production, you should really consider using some server side solution like [the one pointed](http://stackoverflow.com/questions/29975138/how-can-i-get-pngbase64-with-images-inside-of-svg-in-google-charts/30135485#comment-48400932) by @Guest271314 – Kaiido May 11 '15 at 15:05
  • What do you mean consumptive? Heavy load? Taking too long to load entire png? More ram? Because I notice that I see the png keeping adding the images inside. – fsi May 11 '15 at 16:36
  • Yes, a little bit of the three I guess. But this is mainly due to the big size of the image you use. The first snippet can also be data consumptive, in the meaning of a lot of network requests can be done if you've got different images linked in the svg. But what do you mean by "the png keeping adding the images inside"? – Kaiido May 11 '15 at 16:39
  • For the first example, when I refresh the page, I can see the `` updating itself for adding the tex.jpg because after the graphic is generate. But the second, not. – fsi May 11 '15 at 16:46
  • Do you mean the svg `` tag ? I don't see it on my computer, but I may have the cache disabled right now. But anyyway, both examples should make the same use of browser cache. – Kaiido May 11 '15 at 16:58
  • Not ``, its the ``, and just to test how was done the final form. And Yes, both examples works, but id prefer the second one. – fsi May 11 '15 at 17:07
  • Yes for your case, definitely choose this one. – Kaiido May 11 '15 at 17:08
  • @fsi I edited the answer. Have a look since it's not the one you accepted anymore. If it doesn't fit your needs, I could rollback, but IMO, it's a way better approach to achieve this. – Kaiido May 12 '15 at 17:33
  • Im only having trouble with multiple images(`var images = ["..png", "...png"]`) instead of one image. Is it possible to add base64 into array? since array is extendable(increase whenever it needs). So I tried modify on the function `getbase64URI` to loop it and push it to an array. – fsi May 12 '15 at 17:59
  • Yes it should be possible. Check the new code, it may be easier to edit. I'll give you some help tomorrow if you didn't find. (I think you should make a loop which calls getbase64URI, and then the callback to drawVisual...) – Kaiido May 12 '15 at 18:07
  • Yes, I will try to ajust. – fsi May 12 '15 at 18:17
0

This example creates an svg container populated by an image. In my example the image is an svg image but you should be able to put in any type of image (jpg, png, gif). The container is created first then the image is created inside the container.

 // create svg
                var svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
                svg.setAttribute('class','shadowed handle_icon_sensors');
                svg.setAttribute('height','25');
                svg.setAttribute('width','25');
                svg.setAttribute('id',idAttr);
                svg.setAttribute('z-index','21000');
                document.getElementById("zones_container").appendChild(svg);

            // create svg image
            var svgimg = document.createElementNS('http://www.w3.org/2000/svg','image');
            svgimg.setAttribute('height','25');
            svgimg.setAttribute('width','25');
            svgimg.setAttributeNS('http://www.w3.org/1999/xlink','href','svg/icon_sensorYellow.svg');
            svgimg.setAttribute('x','0');
            svgimg.setAttribute('y','0');
            document.getElementById(idAttr).appendChild(svgimg);
Mark Bellamy
  • 135
  • 1
  • 10
  • I didnt get it quite right, you mean, I have the graphic and your `var svg = ...` will transform to svg again(my graphic) to a new one and then I get the image I want? – fsi Apr 30 '15 at 20:08
  • The key is you have to create the svg container (// create svg), then create the image within it (// create svg image). I was referring to my image "'svg/icon_sensorYellow.svg'" being an svg image. But any other web image should work as well. "idAttr" is created by my code to create multiple instances in a for loop, but any id you select will work. Just set the variable equal to your id. – Mark Bellamy May 01 '15 at 00:10
  • Can you help me using the fiddle? I tried to use the url that gives me svg but is not generating the image. – fsi May 01 '15 at 03:26
0

Note, When converted to src of img element retained blue background-color .

Try , after .each()

// set namespace attributes
var svg = $("svg").attr({
    "xmlns": "http://www.w3.org/2000/svg",
    "xmlns:xlink": "http://www.w3.org/1999/xlink"
})[0];

// create `data URI` of `svg`
var dataURI = "data:image/svg+xml;charset=utf-8;base64," + btoa(svg.outerHTML.trim());    

// post `svg` as `data URI` to server
$.post("/path/to/server/", {
    html: dataURI
}, "html")
    .then(function (data) {
    // do stuff
    // `svg` `data URI`
    console.log(data);

}, function (jqxhr, textStatus, errorThrown) {
    console.log(textStatus, errorThrown);
});

jsfiddle http://jsfiddle.net/R8A8P/58/

guest271314
  • 1
  • 15
  • 104
  • 177
  • The SVG didnt rendered properly, `This page contains the following errors: error on line 1 at column 1: Encoding error`. – fsi May 09 '15 at 02:06
  • @fsi Interesting, did not receive error notification here; neither at opened `window` or `data URI` at `console` ? Browser tried at ? – guest271314 May 09 '15 at 03:19
  • @fsi Tried `js` above at chrome, chromium, nightly without any error notifications. If could include linked image as `data URI` before loading "visualization" at page , image at "bars" may not be considered "cross origin" , or "cross domain" . Attempting to create `png` at `canvas` from `cross origin` , perhaps, challenging. One method to actually create a `png` image of the `svg` created above is to utilize firefox web console to create a screenshot of of the `svg`. This does not resolve building the bars with linked cross origin image. See http://stackoverflow.com/q/20584355/2801559 – guest271314 May 10 '15 at 14:51
  • @guest271314 , to do so he actually needs to convert the external images to base64 dataURL and embed them first. After that it will work, at least on non-IE browsers. See [my answer](http://stackoverflow.com/a/30135485/3702797) which does it, in an horrible way. But actually, as you stated in your comment on OP, the server side solution is the best one, tried with Imagick and it does work with external images too. I don't know about inkscape but it may worth the try too. – Kaiido May 11 '15 at 08:02
  • @Kaiido Yes. Attempted to suggest utilizing `data URI` of background "bars" inline , instead of linked to external resource. Did not attempt , here, to do so; unaware of any possible licensing , etc. attached to that specific image ? Also considered suggesting creating linear-gradient as substitute for external link . Tried to open `svg` , with data-uri as src of "bars" at http://stackoverflow.com/a/30135485/2801559; `svg` appear not to have `namespace` of `svg`, `xlink` included at `svg` ? Which image at http://stackoverflow.com/a/30135485/3702797 renders resultant `svg` as `png` ? – guest271314 May 11 '15 at 15:50
  • Hum didn't get the whole thing in that comment. If you want a walkthrough my answer, in the first snippet, the svg loads the images with OP's code, then it searches for `` tags and convert each external resource it found (only new ones). Then it changes the external urls to the newly created dataURL, so we've got a crossorigin compliant svg, with no external resources. Finally, the svg is passed to an `' tag's src as dataURI and drawn to a canvas to get the dataURL of the full svg. – Kaiido May 11 '15 at 16:00
  • The second snippet is quite simpler as it directly converts the external png to dataURL and then applies it to the svg at its creation, saving one request to external resource. Then it uses the same technique to export the full svg as png dataURL – Kaiido May 11 '15 at 16:03
  • @Kaiido How to open `png` of "visualization" at new tab ? – guest271314 May 11 '15 at 16:58
  • In first snippet replace `$('#chartImg')[0].src = d;` in `encodeSVGImages` with `window.open(d)`, and in the second one, the callback is set in the .each loop (which is not a great idea by the way…) – Kaiido May 11 '15 at 17:05
  • @Kaiido Tried replacing `$('#chartImg')[0].src = d;` with `window.open(d)` at first stacksnippets ; no window is opened ? How to accomplish at second stacksnippets ? – guest271314 May 11 '15 at 17:25
  • http://jsfiddle.net/f2Lrvdzo/ for first snippet and http://jsfiddle.net/f2Lrvdzo/1/ for second one – Kaiido May 11 '15 at 17:33
  • 1
    Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/77544/discussion-between-guest271314-and-kaiido). – guest271314 May 11 '15 at 17:41
  • @Kaiido Tried , new `window` not opening at jsfiddle ? – guest271314 May 11 '15 at 17:45
0

this html worked in react

 <a
    href={chart.getImageURI()}
    download="chart.png">
    getImage
  </a>
mindlid
  • 1,679
  • 14
  • 17