HTML5 Canvas - Fill circle with image

I did this the other day for a big thing I'm making;

var thumbImg = document.createElement('img');

thumbImg.src = 'path_to_image';
thumbImg.onload = function() {
    tmpCtx.save();
    tmpCtx.beginPath();
    tmpCtx.arc(25, 25, 25, 0, Math.PI * 2, true);
    tmpCtx.closePath();
    tmpCtx.clip();

    tmpCtx.drawImage(thumbImg, 0, 0, 50, 50);

    tmpCtx.beginPath();
    tmpCtx.arc(0, 0, 25, 0, Math.PI * 2, true);
    tmpCtx.clip();
    tmpCtx.closePath();
    tmpCtx.restore();
};

Worked perfect for me.

Here's a more complex version of it that I made which does image caching too, https://jsfiddle.net/jaredwilli/ex5n5/


Not sure if you are still looking for the answer, but here's how:

var ctx = document.getElementById('your_canvas').getContext("2d");
//ctx.lineWidth = 13; 
//ctx.strokeStyle = 'rgba(0,0,0,1)'; 
//ctx.fillStyle="rgba(0,0,0,0)" // if using this, make sure alpha < 1

ctx.arc(100,100, 50, 0, Math.PI*2,true); // you can use any shape
ctx.clip();

var img = new Image();
img.addEventListener('load', function(e) {
    ctx.drawImage(this, 0, 0, 200, 300);
    //ctx.fill();
//ctx.stroke();
}, true);
img.src="/path/to/image.jpg";

You can also do this with pattern, but you get less image placement flexibility

ctx.arc(100,100, 70, 0, Math.PI*2,true);
ctx.clip();

img = new Image()
img.addEventListener('load', function(e) {
    ctx.fillStyle = ctx.createPattern(this, 'no-repeat') 
    ctx.fill();
}, true);
img.src="/path/to/image.jpg"

Consider using some of these alternatives:

  • Using an <img> with CSS for border-radius: http://jsfiddle.net/ChrisMorgan/BQGxA/

  • Use SVG rather than <canvas> and set the ellipse as the clipping path for an image. (More complex clipping paths are then easy, too)

Not knowing more about your requirements and situation I don't know if these will satisfy your requirements, but I think they're worth while considering. <canvas> isn't the solution to all your problems - for many of these cases, CSS in normal HMTL and/or SVG may be a better match.


The problem with the clip() method is that Chrome will render the borders non antialiased, as shown in this question.

One solution is to use globalCompositeOperation as shown in Daniel's answer:

//set-up - probably only needs to be done once
var scratchCanvas = document.createElement('canvas');
scratchCanvas.width = 100;
scratchCanvas.height = 100;
var scratchCtx = scratchCanvas.getContext('2d');


//drawing code
scratchCtx.clearRect(0, 0, scratchCanvas.width, scratchCanvas.height);

scratchCtx.globalCompositeOperation = 'source-over'; //default

//Do whatever drawing you want. In your case, draw your image.
scratchCtx.drawImage(imageToCrop, ...);


//As long as we can represent our clipping region as a single path, 
//we can perform our clipping by using a non-default composite operation.
//You can think of destination-in as "write alpha". It will not touch
//the color channel of the canvas, but will replace the alpha channel.
//(Actually, it will multiply the already drawn alpha with the alpha
//currently being drawn - meaning that things look good where two anti-
//aliased pixels overlap.)
//
//If you can't represent the clipping region as a single path, you can
//always draw your clip shape into yet another scratch canvas.

scratchCtx.fillStyle = '#fff'; //color doesn't matter, but we want full opacity
scratchCtx.globalCompositeOperation = 'destination-in';
scratchCtx.beginPath();
scratchCtx.arc(50, 50, 50, 0, 2 * Math.PI, true);
scratchCtx.closePath();
scratchCtx.fill();


//Now that we have a nice, cropped image, we can draw it in our
//actual canvas. We can even draw it over top existing pixels, and
//everything will look great!

ctx.drawImage(scratchCanves, ...);