Reputation: 6280
Is there a way to clip an image to an SVG shape with a CanvasRenderingContext2D
?
I'm trying to use different SVG shapes to display various parts of an image on demand.
For example – an SVG with an <ellipse>
element (or an equivalent <path>
) would allow me to show a circular portion of the image.
CanvasRenderingContext2D.clip() seems to be the close to what I need, but I can't find any information about how to use it with an SVG, or alternatively how to draw an SVG as a path.
Another direction I'm thinking about is saving the clipping area as a <path>
element and manually transforming it to CanvasRenderingContext2D
equivalent methods such as lineTo
and arc
.
Upvotes: 2
Views: 1798
Reputation:
You cannot do this directly. You can:
Here is an example of how you can build a simple parser for SVG. It cannot be used for generic use but assumes you know the SVG in question. You can build on top of this to support different units, transform lists etc.
The path that is extracted can be stored on a Path2D object instead of directly as shown below (Path2D may need poly-fill in some browsers), or store custom objects/arrays etc. This is entirely up to you.
The example is more a proof-of-concept.
var ctx = c.getContext("2d"),
mask = document.getElementById("mask"); // get a SVG element
// some random graphics
for(var i=30,r=Math.random;i--;) {
ctx.fillStyle = "hsl(" + (360*r()) + ",50%,50%)";ctx.fillRect(280*r(),120*r(),50,50)}
// parse SVG element
if (mask.localName) {
switch(mask.localName) {
case "rect":
ctx.rect(v("x"), v("y"), v("width"), v("height"));
break;
case "ellipse":
// need polyfill in some browsers
ctx.ellipse(v("cx"), v("cy"), v("rx"), v("ry"), 0, 6.28);
break;
// more cases here
}
// use path from SVG to clip
ctx.globalCompositeOperation = "destination-in";
ctx.fill();
}
// helper - obtains a numeric value for SVG element property
function v(name) {return mask[name].baseVal.value}
#c, svg {border:1px solid #777}
<h4>SVG (showing mask)</h4>
<svg xmlns="http://www.w3.org/2000/svg"
width="300" height="150">
<rect id="mask" x="50" y="30" width="200" height="100" />
</svg>
<h4>Canvas (mask applied from SVG)</h4>
<canvas id=c></canvas>
Upvotes: 0
Reputation: 105035
@Kaiido has some good suggestions for you (see his comment to the question).
Draw the SVG to canvas & use globalCompositeOperation instead of clipping. But Firefox has a bug in applying gCO directly to an svg image, which force you to first draw your svg on a second canvas ; and that IE prior to Edge will taint the canvas when an svg is drawn to it.
Parse your svg first and then use the canvas API to draw those shapes (path commands are quite similar so it's not so hard and library like fabricjs can even handle it in a nice way for you)
Another option is to convert your SVG drawings to .png format and use that image + globalCompositeOperation to clip your image inside the .png shape. This avoids the cross-browser problems with SVG.
But, if your clipping shapes are just simple SVG paths (ovals, etc), then you might forget SVG and draw your path using canvas path commands.
I'll repost a previous SO Q&A to illustrate clipping inside a canvas path:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/kidwallpaper.jpg";
function start(){
// resize the canvas to equal the image size
var iw=img.width;
var ih=img.height;
cw=canvas.width=iw;
ch=canvas.height=ih;
// calculate the scaling needed to max the display of the image
// inside the oval
if(iw>ih){
var scaleX=iw/ih
var scaleY=1;
var r=ih/2;
}else{
var scaleX=1;
var scaleY=ih/iw;
var r=iw/2;
}
// scale so the circle (arc) becomes an oval
ctx.scale(scaleX,scaleY);
ctx.arc(cw/scaleX/2,ch/scaleY/2,r,0,Math.PI*2);
ctx.fill();
// undo the scaling
ctx.scale(1/scaleX,1/scaleY);
// draw the image centered inside the oval using compositing
ctx.globalCompositeOperation='source-atop';
ctx.drawImage(img,cw/2-img.width/2,ch/2-img.height/2);
ctx.globalCompositeOperation='source-atop';
}
body{ background-color: black; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>
Upvotes: 1
Reputation: 324790
Three steps:
context.globalCompositeOperation = 'source-in';
The image will only be drawn where the SVG has already filled in pixels with colour, effectively clipping the image to whatever has already been drawn.
You can either set the globalCompositeOperation
back to 'source-over'
(the default), or use context.save()
before and context.restore()
after to put the canvas back into a "normal" drawing mode.
Upvotes: 1