i have a chart that created with highchart.
i need to save svg to png in internet Explorer.
i use from follow code and exist security Error in ie11.
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
var imgChart = document.createElement('img');
imgChart.setAttribute('src', 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg))));
imgChart.onload = function () {
ctx.drawImage(imgChart, 0, 0);
var blobObject = canvas.msToBlob();
window.navigator.msSaveBlob(blobObject, 'save.png');
})
I didn't found an satisfying dupe target, so I'll rewrite it as an answer :
Drawing an SVG image through drawImage method will taint the canvas in IE < Edge for security reasons.
This operation is somehow sensitive for browsers, since svg images imply to parse some XML, and that it can contain some tricky elements (though IE doesn't support <foreignObject>...)
So quite often, browsers will add security restrictions when SVG images are drawn to it, and will block all exporting methods.
This is the case in safari > 9 when an <foreignObject> is drawn on it, this was also the case in chrome, but only when the image comes from an Blob (an implementation bug, but they finally leveraged the security restriction altogether anyway).
And then in IE < Edge, with any SVG.
The only way to workaround this issue is to parse yourself the SVG, and then use the canvas' methods to reproduce it.
This is all doable, but can take some time to implement, so even though I don't really like it, you'd probably be better using an library like canvg, which does exactly this (parsing + rendering with canvas methods).
Related
I'm having problems to import SVGs to the canvas and using setZoom() with the FabricJS. I'm using version "2.0.0.rc4".
I've been trying to import them using two methods, but each one has different problems:
1- loadSVGFromURL
fabric.loadSVGFromURL(src, function(objects, options) {
let loadedObjects = new fabric.Group(group);
var obj = fabric.util.groupSVGElements(objects, options);
canvas.add(obj).renderAll();
});
With this method some SVGs load incorrectly on the canvas, but the zoom works perfectly.
loadSVGFromURL loads the SVG incorrectly
2- new fabric.Image
const image = new Image();
image.crossOrigin = "Anonymous";
image.src = src;
let imageObject;
image.onload = () => {
imageObject = new fabric.Image(image, {
scaleY: 1,
scaleX: 1,
cropX: 0,
cropY: 0,
lockUniScaling: true,
crossOrigin: 'Anonymous'
});
}
With this method every SVG is correctly imported, but when I try to use the zoom in my app, the shapes of the SVG inside the viewbox (container) adjust their size independently, like it's being masked, cropped or clipped. I guess is something related to the preserveAspectRatio property, but I can't make it work.
This is the method I'm using for setting the zoom. The method works correctly for the canvas and other objects, except the SVGs imported with the method previously described.
setCanvasZoom(value) {
// value is from 10 to 500.
// the zoomFactor will result in an integer from 0.1 to 5
let zoomFactor = parseInt(value, 10) / 100;
this.canvas.setZoom(zoomFactor);
this.canvas.setWidth(this.templateDimensions.width * zoomFactor);
this.canvas.setHeight(this.templateDimensions.height * zoomFactor);
this.canvas.renderAll();
}
The shapes are adjusted independently to the container
Am I doing something wrong using the first method to import SVGs? I tried optimizing the SVGs with svgo and also edit them in Illustrator but with no success (in fabricjs/kitchensink loads incorrectly as well).
Does exists a way to lock the SVGs inside the container, using the second method? Should I use another method to set the zoom?
I'd really appreciate any help with these issues.
there was indeed a bug in circle and ellipse parsing at the time of writing this question. circles were inheriting width and height from the svg document overriding radius value and displacing them.
The bug has been fixed here,
https://github.com/kangax/fabric.js/pull/4637/files#diff-35fc8e842fb0e1e1953d9ba21a292160R189
the bug has been introduced between rc3 and rc4 but now you can import the path normally without preprocessing the SVG, provided you download last version.
The problem was with the SVGs files, the circle tags were imported incorrectly to the FabricJS canvas, placing the objects in other places, even outside the original viewBox.
I tested both versions of the SVG in FabricJS Kitchensink:
Screenshot with the differences between SVG imported with <circle> tags and <circle> tags converted to <paths>
I used svgo to process all the SVGs files with the option of convertShapeToPath included and the parameter convertArcs set as true.
{
"plugins": [
{"convertShapeToPath": {"convertArcs": true}},
]
}
It's also possible to convert the primitive shapes to paths in Illustrator selecting the object and making a compound path of it (menu Object > Compound path > Create or just cmd ⌘ + 8).
I couldn't find an issue on GitHub or Stack Overflow about this problem, so I really hope this solution could help others facing the same problem.
Thank you very much to the developers and maintainers of FabricJS for the amazing work!
Gists of SVGs files:
SVG with <circle> tags
SVG with <circle> tags converted to <path>
How do we get the Pixel data from images in Elm?
Here in JavaScript, is code to get the color of a set of pixels in a figure (taken from here)
var image = new Image;
image.src = "starry-night.jpg";
var canvas = d3.select("body").append("canvas");
var context = canvas.node().getContext("2d");
context.drawImage(image, 0, 0);
// beware variable name "image" got used twice
image = context.getImageData(0, 0, width, height);
var x = Math.random()*width,
y = Math.random()*height,
i = (y * width + x) << 2;
pixelColor = d3.rgb(image.data[i + 0], image.data[i + 1], image.data[i + 2]) + "";
The code loads an image to a <canvas> element, then extracts the color of a pixel from the canvas using image.getImageData().
Can we interface the image.data object in Elm? Right now I don't think it's possible...
Right now Collage types are list of forms...
HTML is also a module that can put imags in the DOM.
SVG allows for some simple global image transformations but nothing at the pixel level
Elm has the Virtual Dom. In fact of problems like this, might be addressed in virtual-dom which is lower level so we are not encouraged to do this directly.
However, Elm makes a clear distinction between Collage elements and SVG elements, with no clear interface to the getImageData() function.
Do I write my own with Elm's new interOp feature?
Does a way already exist in Elm? Or a new one has to be written?
JavaScript
The << operator is called Left Shift
As suggested by #SimonH, use a port to JS until Elm provides a first-hand way to do so (if it ever does). The same approach would apply to anything you can't yet do in Elm.
I'm just answering as an answer rather than a comment for the sake of others who come here.
How to convert an Image(png) to byte code in Javascript. I've used this code but in IE8 there is no use in this code because there is no support of canvas element in IE8.
function getBase64Image(){
p=document.getElementById("fileUpload").value;
img1.setAttribute('src', p);
canvas.width = img1.width;
canvas.height = img1.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img1, 0, 0);
var dataURL = canvas.toDataURL("image/png");alert("from getbase64 function"+dataURL );
return dataURL;
}
Is any other way to get image byte code in IE8. I need either from Image to base64 byte code in a html page or from any image url base64 byte code.
My image url is like this is there any other way to get image byte code in javascript.
Simple answer is unfortunately you can't - out of the box.
As you say, IE8 does not support the canvas element so there is no way to extract the image data as bytes as you would need to go by the canvas and then use toDataURL or getImageData.
There are poly-fills for IE8 that allows you to use the basic functions such as excanvas. This however does not support pixel extraction as with the two above mentioned metods.
There are two work-arounds:
Use server: send image to server and process it there
Use Flash-based canvas "poly-fills" which allow you to do this.
For the latter point there are a few options such as this one:
http://flashcanvas.net/
The standard way to deal with situations where the browser does not support the HTML5 <canvas> tag is to embed some fallback content like:
<canvas>Your browser doesn't support "canvas".</canvas>
But the rest of the page remains the same, which may be inappropriate or misleading. I'd like some way of detecting canvas non-support so that I can present the rest of my page accordingly. What would you recommend?
This is the technique used in Modernizr and basically every other library that does canvas work:
function isCanvasSupported(){
var elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
}
Since your question was for detection when it's not supported, I recommend using it like so:
if (!isCanvasSupported()){ ...
There are two popular methods of detecting canvas support in browsers:
Matt's suggestion of checking for the existence of getContext, also used in a similar fashion by the Modernizr library:
var canvasSupported = !!document.createElement("canvas").getContext;
Checking the existence of the HTMLCanvasElement interface, as defined by the WebIDL and HTML specifications. This approach was also recommended in a blog post from the IE 9 team.
var canvasSupported = !!window.HTMLCanvasElement;
My recommendation is a variation of the latter (see Additional Notes), for several reasons:
Every known browser supporting canvas ― including IE 9 ― implements this interface;
It's more concise and instantly obvious what the code is doing;
The getContext approach is significantly slower across all browsers, because it involves creating an HTML element. This is not ideal when you need to squeeze as much performance as possible (in a library like Modernizr, for example).
There are no noticeable benefits to using the first method. Both approaches can be spoofed, but this not likely to happen by accident.
Additional Notes
It may still be necessary to check that a 2D context can be retrieved. Reportedly, some mobile browsers can return true for both above checks, but return null for .getContext('2d'). This is why Modernizr also checks the result of .getContext('2d'). However, WebIDL & HTML ― again ― gives us another better, faster option:
var canvas2DSupported = !!window.CanvasRenderingContext2D;
Notice that we can skip checking for the canvas element entirely and go straight to checking for 2D rendering support. The CanvasRenderingContext2D interface is also part of the HTML specification.
You must use the getContext approach for detecting WebGL support because, even though the browser may support the WebGLRenderingContext, getContext() may return null if the browser is unable to interface with the GPU due to driver issues and there is no software implementation. In this case, checking for the interface first allows you to skip checking for getContext:
var cvsEl, ctx;
if (!window.WebGLRenderingContext)
window.location = "http://get.webgl.org";
else {
cvsEl = document.createElement("canvas");
ctx = cvsEl.getContext("webgl") || cvsEl.getContext("experimental-webgl");
if (!ctx) {
// Browser supports WebGL, but cannot create the context
}
}
##Performance Comparison
Performance of the getContext approach is 85-90% slower in Firefox 11 and Opera 11 and about 55% slower in Chromium 18.
I usually run a check for getContext when I create my canvas object.
(function () {
var canvas = document.createElement('canvas'), context;
if (!canvas.getContext) {
// not supported
return;
}
canvas.width = 800;
canvas.height = 600;
context = canvas.getContext('2d');
document.body.appendChild(canvas);
}());
If it is supported, then you can continue the canvas setup and add it to the DOM. This is a simple example of Progressive Enhancement, which I (personally) prefer over Graceful Degradation.
Why not try modernizr ? It's a JS library that provides detection capability.
Quote:
Have you ever wanted to do
if-statements in your CSS for the
availability of cool features like
border-radius? Well, with Modernizr
you can accomplish just that!
try {
document.createElement("canvas").getContext("2d");
alert("HTML5 Canvas is supported in your browser.");
} catch (e) {
alert("HTML5 Canvas is not supported in your browser.");
}
There may be a gotcha here- some clients do not support all canvas methods.
var hascanvas= (function(){
var dc= document.createElement('canvas');
if(!dc.getContext) return 0;
var c= dc.getContext('2d');
return typeof c.fillText== 'function'? 2: 1;
})();
alert(hascanvas)
You can use canisuse.js script to detect if your browsers supports canvas or not
caniuse.canvas()
If you're going to get the context of your canvas, you might as well use it as the test:
var canvas = document.getElementById('canvas');
var context = (canvas.getContext?canvas.getContext('2d'):undefined);
if(!!context){
/*some code goes here, and you can use 'context', it is already defined*/
}else{
/*oof, no canvas support :(*/
}
I'm using the Canvas object with javascript. Just doing some tests to see how fast I can set pixels in a draw loop.
On mac, it works great in FF, safari, chrome. On windows, I get a flickering effect on FF and chrome. It looks like somehow the canvas implementation on windows is different than on mac for the different browsers? (not sure if that's true).
This is the basic code I'm using to do the drawing (taken from the article below - I've optimized the below to tighten the draw loop, it runs pretty smooth now):
var canvas = document.getElementById('myCanvasElt');
var ctx = canvas.getContext('2d');
var canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (var x = 0; x < canvasData.width; x++) {
for (var y = 0; y < canvasData.height; y++) {
// Index of the pixel in the array
var idx = (x + y * canvas.width) * 4;
canvasData.data[idx + 0] = 0;
canvasData.data[idx + 1] = 255;
canvasData.data[idx + 2] = 0;
canvasData.data[idx + 3] = 255;
}
}
ctx.putImageData(canvasData, 0, 0);
again, browers on windows will flicker a bit. It looks like the canvas implementation is trying to clear the canvas to white before the next drawing operation takes place (this does not happen on mac). I'm wondering if there is a setting I can change in the Canvas object to modify that value (double-buffering, clear before draw, etc)?
This is the article I am using as reference:
http://hacks.mozilla.org/2009/06/pushing-pixels-with-canvas/
Thanks
I think it's fairly clear that browsers who implement the Canvas object use DIBS (device independent bitmaps). The fact that you have access to the pixelbuffer without having to lock the handle first is proof of this. And Direct2D has nothing to do with JS in a browser thats for sure. GDI is different since it uses DDBs (device dependent bitmaps, i.e allocated from video memory rather than conventional ram). All of this however has nothing to do with optimal JS rendering speed. I think writing the RGBA values as you do is probably the best way.
The crucial factor in the code above is the call to putImageData(). This is where browsers can differ in their implementation. Are you in fact writing directly to the DIB, and putImageData is simply a wrapper around InvalidateRect? Or are you in fact writing to a duplicate in memory, which in turn is copied into the canvas device context? If you use linux or mac then this is still a valid question. Although device contexts etc. are typically "windows" terms, most OS'es deal with handles or structures in pretty much the same way. But once again, we are at the mercy of the browser vendor.
I think the following can be said:
If you are drawing many pixels in one go, then writing directly to the pixelbuffer as you do is probably the best. It is faster to "bitblt" (copy) the pixelbuffer in one go after X number of operations. The reason for this is that the native graphics functions like FillRect also calls "invalidate rectangle" which tells the system that a portion if the screen needs a re-draw (refresh). So if you call 100 line commands, then 100 update's will be issued - slowing down the process. Unless (and this is the catch) you use the beginPath/EndPath methods as they should be used. Then it's a whole different ballgame.
It's here that the Begin/End path "system" comes into play, and also the Stroke/Outline commands. They allow you to execute X number of drawing operations within a single update. But a lot of people get this wrong and issue a redraw for each call to line/fillrect etc.
Also, have you tried creating an invisible canvas object, drawing to that, and then copying to a visible canvas? This could be faster (proper double-buffering).
The problem is with the way the browsers use the native graphics APIs on the different OSes. And even on the same OS, using different APIs (for example GDI vs. Direct2D in Windows) would also produce different results.