Thursday, September 12, 2013

SVG in HTML 5 canvas: tainted canvas

Introduction

I had a task to create a JS component which will build the halls scheme. I decide to use HTML5 canvas and SVG as an image for chairs. Canvas - because he is a fast. SVG - for easy scaling.
"Ghost" canvas for medium hall
Simple example
I also need to understand, which the place are chosen by user when he clicked on it. The main highlight is that the seat may be of any arbitrary shape and be rotated to any angle. Specially for this I draw an invisible canvas with chairs where each chair painted in unique color. 

Once I have a copy hall scheme on invisible layer I can find a chair what user clicked by picking color of the invisible chair on mouse hover (don't confuse it with JS onmouseover event). But here is waiting for us a little surprise. I can draw SVG on canvas, but I can't call such methods: context.getImageData and canvas.toDataUrl! What's the matter?

Problem

Look the code below:


<canvas></canvas>
<script>
    var canvas = document.getElementsByTagName('canvas')[0],
        ctx = canvas.getContext('2d'),
        img = new Image();
    img.onload = function() {
        ctx.drawImage(img, x, y, w, h);
        console.log(ctx.getImageData(x, y, w, h));
    }
    img.src = 'pathTo.svg';
</script>


If you try to do something like this in Chrome, you will get an error: "Unable to get image data from canvas because the canvas has been tainted by cross-origin data". And then "Uncaught Error: SecurityError: DOM Exception 18". Run this snippet and and check it out. Open your developer tools panel and look at the console.
Why did this happened you may ask? I found the answer here and here.
The most important things in a few words:
There's nothing native that allows you to natively use SVG paths in canvas. You must convert yourself or use a library to do it for you.
If the source attribute of HTML img-element is set to "smth.svg", and this image element is rendered on Canvas element, the origin-clean is set to false even if same origin policy should be satisfied.
If the SVG is represented as a data URI: "data:image/svg+xml;base64,dVAx..." and this string is set as source attribute of a HTML image, which is then rendered on Canvas element, the origin-clean also sets to false.
SVG with Safe Elements. This testing becomes more interesting if we do the same tests with a SVG that uses only safe elements, such as <rect> and <circle>. Result will be that the Canvas origin-clean property sets permanently to false, even if all the elements can be considered as "safe" and same origin.
Personally I, Patrick from Chromium issue discussions and Antony from Stackoverflow think, that problem is in SVG namespace declarations which is located at http://www.w3.org/, which is of a different origin.

Decision

If it is important for you to use exactly а SVG you may use external library canvg. Note the method drawSvg() which are expands a context object.


ctx.drawSvg('pathTo.svg', x, y, w, h);

This method redraws SVG with native canvas API.
But I don't use this library, because this solution are too expensive for us.

I use SVG images to draw visible part and png image for ghost canvas generation. I don't use images on both part, because canvas has problem with anti-aliasing after image resize and special CSS styles do not help.

Epilogue

Hope this article save your time.

No comments:

Post a Comment