I am trying to convert svg to png, using the code here as basis of my conversion.
Please note the following code (it has been shortened to only what's relevant to the error that I will describe later):
let svgData = new XMLSerializer().serializeToString(target); // 'target' is an svg element that is passed to us
let canvas = document.createElement('canvas');
let ctxt = canvas.getContext('2d');
let img = document.createElement('img');
img.setAttribute(
'src',
'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svgData)))
);
ctxt.drawImage(img, 0, 0);
The above code works fine. However there is a problem with it. Notice that here is this line of code:
'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svgData)))
In this line of code, the unescape() function is used, however it is deprecated, and I have to use an alternative. According to documentation, I should use decodeURIComponent(), but this not a viable solution, because when I update the code above with decodeURIComponent(), the result is:
'data:image/svg+xml;base64,' + window.btoa(decodeURIComponent(encodeURIComponent(svgData)))
So basically I would be encoding then decoding. Which is the same as doing this:
'data:image/svg+xml;base64,' + window.btoa(svgData)
Now, with the new updated code, if the source svg contains only English characters, everything is OK. However, if an svg contains characters other than English (e.g., contains the Japanese word トマト), then the next line of code, ctxt.drawImage(img, 0, 0); , throws an error. The error message is DOMException: String contains an invalid character.
So, to summarise so far:
the original code works fine with both English and non-English characters, but uses the unescape() function which is depracated. So I should use something else.
The updated code, which does not encode or decode, causes an exception when using non-English characters.
Now, question 1, what do I do to be able to create an image from an svg that contains non-English characters using base64?
As an alternative solution, I did this
'data:image/svg+xml;utf8,' + svgData
Basically, I did not use base64, but rather utf8. This worked just fine, but I am not sure if it is the correct solution.
So, question 2, is using utf8 a common practice? Is there any issue with it as opposed to base64?
Thank you.
According to the comment below from #RobertLongson , I updated the code to:
'data:image/svg+xml;utf8,' + encodeURIComponent(svgData)
This worked in svg elements containg either English and non-English characters.
Now, question 3, is this correct?
Apologies about the basic question. I am not familiar with this. Thanks.
With this example I'm trying to avoid using the mentioned functions for converting to base64 and instead use a FileReader and particular the readAsDataURL function for returning the data URI. I know the code is a bit more complicated with the callbacks, but it works.
As I understand Google Translate トマト translates into 🍅.
let svg01 = document.getElementById('svg01');
let canvas = document.getElementById('canvas01');
let img = document.getElementById('img01');
let ctx = canvas.getContext('2d');
canvas01.width = svg01.getAttribute('width');
canvas01.height = svg01.getAttribute('height');
let svgData = new XMLSerializer().serializeToString(svg01);
// create a File object
let file = new File([svgData], 'svg.svg', {
type: "image/svg+xml"
});
// and a reader
let reader = new FileReader();
reader.addEventListener('load', e => {
let img = new Image();
// wait for it to got load
img.addEventListener('load', e => {
// update canvas with new image
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(e.target, 0, 0);
// create PNG image based on canvas
img01.src = canvas.toDataURL("image/png");
});
img.src = e.target.result;
});
// read the file as a data URL
reader.readAsDataURL(file);
<p>SVG embedded:</p>
<svg id="svg01" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 20" width="300" height="60">
<rect width="100" height="20" fill="silver" rx="5"/>
<text font-size="7" x="50" y="10" text-anchor="middle"
dominant-baseline="middle">Japanese word: トマト 🍅</text>
</svg>
<p>Canvas image:</p>
<canvas id="canvas01"></canvas>
<p>PNG image:</p>
<p><img id="img01" /></p>
Related
I wrote a script to add an image from my Google Drive and some custom text to a Google Doc. (I got the image insertion code from here).
The resulting document is created ok, but my image is added twice for some reason...
function myFunction(e) {
var doc = DocumentApp.create('fileTest');
var body = doc.getBody();
var matchedFiles = DriveApp.getFilesByName('logo.png');
if (matchedFiles.hasNext()) {
var image = matchedFiles.next().getBlob();
var positionedImage = body.getParagraphs()[0].addPositionedImage(image);
}
body.appendParagraph('Test line of text for testing');
doc.saveAndClose();
}
However, if I get rid of my appendParagraph code (body.appendParagraph(t1);) I only get one image (but obviously without the paragraph of text I want)
What's going on here? And how do I add both one picture and my paragraph of text?
I have not even the slightest clue as to why, but I found a way to make this work.
Switching the order of my code seemed to do the trick. I simply moved the image-insertion code to the end (i.e., after the appendParagraph code), and it worked fine. No duplicate image!
function myFunction(e) {
var doc = DocumentApp.create('fileTest');
var body = doc.getBody();
body.appendParagraph('Test line of text for testing');
var matchedFiles = DriveApp.getFilesByName('logo.png');
if (matchedFiles.hasNext()) {
var image = matchedFiles.next().getBlob();
var positionedImage = body.getParagraphs()[0].addPositionedImage(image);
}
doc.saveAndClose();
}
I am using iScroll5 in a PhoneGap project. On the index page, user will click on a series of thumbnails generated from a database, then the image ID chosen will be written to localstorage, the page will change, the image ID will be pulled from localstorage and the image displayed.
It works fine if I reference the image directly (not from the DB) this way (as a test):
<body onload="loaded()">
<div id='wrapper'><div id='scroller'>
<ul><li><a id='output' href='index.html' onclick='returnTo()'></a></li></ul>
</div></div>
<script>
var newWP = document.createElement('img');
newWP.src = '0buggies/0118_buggies/wallpaper-18b2.jpg';
document.getElementById('output').appendChild(newWP);
</script>
</body>
I can pinch/zoom to resize the image for the screen (the main function my app requires), and scroll the image on the X and Y axis, then upon tapping the image, I will be returned to the index page. All of this works.
But if I pull the image out of a database and reference it the following way, all other aspects of the page code being the same, pinch/zoom does not work, though the picture is displayed and I can scroll on X and Y:
// ... DB code here ...
function querySuccess(tx, results) {
var path = results.rows.item.category +
"/" + results.rows.item.subcat +
"/" + results.rows.item.filename_lg;
document.getElementById("output").innerHTML = "<img src='" + path +
"'>";
}
// ... more DB code here ...
<body onload="loaded()">
<div id='wrapper'> <ul><li><a id='output' href='index.html'
onclick='returnTo()'></a></li></ul> </div>
How do I make iScroll5 work when the image is generated from a DB? I'm using the same CSS and iScroll JS on both pages. (iScroll4 has the same problem as iScroll 5 above.) I am using the SQLite DB plugin (from http://iphonedevlog.wordpress.com/2014/04/07/installing-chris-brodys-sqlite-database-with-cordova-cli-android/ which is my own site).
Try calling refresh on the scrollbar to get it to recognize the DOM change.
Best to wrap it in a 0-delay setTimeout, like so (Stolen from http://iscrolljs.com/#refresh)
:
setTimeout(function () {
myScroll.refresh();
}, 0);
If it takes time for the image to load, you'll want to wait until it's loaded entirely, unless you know the dimensions up-front.
When dealing with images loaded dynamically things get a little more complicated. The reason is that the image dimensions are known to the browser only when the image itself has been fully loaded (and not when the img tag has been added to the DOM).
Your best bet is to explicitly declare the image width/height. You'd do this like so:
function querySuccess (results) {
var path = results.rows.item.category +
"/" + results.rows.item.subcat +
"/" + results.rows.item.filename_lg;
var img = document.createElement('img');
img.width = 100;
img.height = 100;
img.src = path;
document.getElementById('output').appendChild(img);
// need to refresh iscroll in case the previous img was smaller/bigger than the new one
iScrollInstance.refresh();
}
If width/height are unknown you could save the image dimensions into the database and retrieve them together with the image path.
function querySuccess (results) {
var path = results.rows.item.category +
"/" + results.rows.item.subcat +
"/" + results.rows.item.filename_lg;
var img = document.createElement('img');
img.width = results.width;
img.height = results.height;
img.src = path;
document.getElementById('output').appendChild(img);
// need to refresh iscroll in case the previous img was smaller/bigger than the new one
iScrollInstance.refresh();
}
If you can't evaluate the image dimensions in any way then you have to wait for the image to be fully loaded and at that point you can perform an iScroll.refresh(). Something like this:
function querySuccess (results) {
var path = results.rows.item.category +
"/" + results.rows.item.subcat +
"/" + results.rows.item.filename_lg;
var img = document.createElement('img');
img.onload = function () {
setTimeout(iScrollInstance.refresh.bind(iScrollInstance), 10); // give 10ms rest
}
img.onerror = function () {
// you may want to deal with error404 or connection errors
}
img.src = path;
document.getElementById('output').appendChild(img);
}
Why is the viewport user-scalable prop different on each sample? works=no, broken=yes
Just an observation.
fwiw, here are a few things to look into:
Uncomment the deviceReady addListener, as Cordova init really depends on this.
Your loaded() method assigns myScroll a new iScroll, then explicitly calls onDeviceReady(), which then declares var myScroll; -- this seems inherently problematic - rework this.
If 1 & 2 don't help, then I suggest moving queryDB(tx); from populateDB() to successCB() and commenting out the myScroll.refresh()
And just a note, I find that logging to console is less intrusive than using alerts when trying to track down a symptom that seems to be messing with events firing, or timing concerns.
I would like to two show a Chart and also present an url with a link to a bigger Chart. The small preview-image looks and works fine:
<canvas id="image"></canvas>
var ct1 = document.getElementById("image").getContext("2d");
ct1.canvas.width = document.getElementById("image").offsetWidth;
ct1.canvas.height = document.getElementById("image").offsetHeight;
var Chart1 = new Chart(ct1).Line(lineChartData1,options);
The canvas is wrapped in a div, that's why offsetWidth and offsetHeight (to fill this additional div-element). Cause of the responsive-design there is no fixed image. Anyway, this works perfectly. For the URL to the "bigger" image I want to have the URL. I know the toDataURL() will help.
var url = document.getElementById("image").toDataURL();
document.write(url);
There are two disturbing problems with it:
The URL with this way exisists and, but the image has no content.
I also want to give the canvas-image a new size, like I managed with ct1.canvas.width and ct1.canvas.height, but it seems I cannot add this to the toDataURL.
What's wrong with the code?
Okay, I think I got it. Chart.js is animating the charts, so the toDataURL() I mentioned in my first question rendered only an empty image. We have to initiate the toDataURL, not before the animation is done. We handle that with the options:
var options = {
onAnimationComplete: done
}
and a tiny function:
function done() {
console.log('done');
var url=document.getElementById("canvas").toDataURL();
document.getElementById("canvas_link").href=url;
}
I think that's all.
function pushImage () {
var img = new Image();
img.src = '/waroot/chart/chart.png';
document.getElementById("test1").innerHTML = "<img src='/waroot/chart/chart.png'>";
document.getElementById("test2").innerHTML = img;
}
Test 1 works and shows the image, but test 2 doesn't. I am not sure how to solve it but i will need the way test 2 works further along my project since i'm going to have to circle through a large amount of images.
The images are created by JFreeCharts, saved to png and then have to be posted to a site. As a side question: is it possible to push the freecharts objects straight to the jscript instead of having to save them prior (i could throw them into the dictionary and then look them up later but i'm not sure if this works)
Use .appendChild(img) instead of .innerHTML:
function pushImage () {
var img = new Image();
img.src = '/waroot/chart/chart.png';
document.getElementById("test1").innerHTML = "<img src='/waroot/chart/chart.png'>";
document.getElementById("test2").appendChild(img);
}
Demo
This is because img is an image object, not an html string, so you have to append it to the DOM.
P.S., don't forget that the alt attribute is required in the img tag!
I got this web service that gives me a (jpeg) image. What I want is take this image, convert it into a Data URI and display it on an HTML5 canvas, like that:
obj = {};
obj.xmlDoc = new window.XMLHttpRequest();
obj.xmlDoc.open("GET", "/cgi-bin/mjpegcgi.cgi?x=1",false, "admin", "admin");
obj.xmlDoc.send("");
obj.oCanvas = document.getElementById("canvas-processor");
obj.canvasProcessorContext = obj.oCanvas.getContext("2d");
obj.base64Img = window.btoa(unescape(encodeURIComponent( obj.xmlDoc.responseText )));
obj.img = new Image();
obj.src = 'data:image/jpeg;base64,' + obj.base64Img;
obj.img.src = obj.src
obj.canvasProcessorContext.drawImage(obj.img,0,0);
Unfortunately, this piece of code doesn't work; the image is not painted on the canvas at all (plus it seems to have width and height = 0, could it be not decoded correctly? I get no exceptions). img.src looks like data:image/jpeg;base64,77+977+977+977+9ABBKRklG....
Resolved: turns out I should have overridden the mime type with:
req.overrideMimeType('text/plain; charset=x-user-defined');
and set the response type with:
req.responseType = 'arraybuffer';
(see this. You should make an asynchronous request if you change the response type, too).
First you need to create an img element (which is hidden)
Then you do exactly what you have done except that you listen to your onload event on your img element.
When this event is launched you are able to get the width and height of your pictures so you can set your canvas to the same size.
The you can draw your image as you did in last line.