35

How can I draw an image inside a circle? If I do:

context.beginPath();
context.arc((e.pageX),(e.pageY),161,0,Math.PI*2,true);
context.closePath();

How can I then use fill() to fill it with my drawn image?

tbleckert
  • 3,763
  • 4
  • 32
  • 39

4 Answers4

67

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/

jaredwilli
  • 11,762
  • 6
  • 42
  • 41
  • 2
    While this was ultimately what I had come up with as a solution in canvas, after highly augmenting and modifying it with all sorts of stuff which needed to be done for the app I worked on, it turned out that 2 days before launching something came up and had 2 days to do an alternative solution which turned out to be far far better faster and more cross-browser for both desktop and mobile. The solution was the answer @MatTheCat suggested "Using an with CSS for border-radius". Just used border-radius: 100% to make it circular. Best solution hands down by far. Just sayin... – jaredwilli May 21 '13 at 23:55
  • Just wanted to add, that it is best practice to set the src *after* setting the `onload` callback. I.e. `thumbImg.src = 'path_to_image';` becomes the last line – Raul Pinto Aug 24 '13 at 11:29
  • True it is best to set the src after the onload. However, I found back when I posted this answer anyways, that it does not really make a big difference unless you are using this like I was, and running hundreds of images through it as quickly as you can so they displayed without showing a broken image in the canvas once drawn. Then again, at that point it's best to use deferreds too, which I did end up doing. – jaredwilli Aug 25 '13 at 18:10
  • Could you please add jsfiddle for this tmpCtx this is undefined for me – Pravin W Aug 25 '16 at 09:25
  • @PravinWaychal Here's a fiddle I made way back when I posted this answer. It was one version of my implementation for what I needed to do at the time which was pretty complex stuff. But you should get the idea. https://jsfiddle.net/jaredwilli/ex5n5/ – jaredwilli Sep 23 '16 at 18:05
14

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"
moby
  • 141
  • 3
5

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, ...);
Community
  • 1
  • 1
Jesús Carrera
  • 11,275
  • 4
  • 63
  • 55
5

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.

Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
  • SVG might be the way to go. What I'm doing is, I have a video and when I pause it, a magnifier pops up. Then with canvas I draw in image of the video on the canvas and zoom it. The problem is that the magnifier is round, so I need to either clip it or fill an arc with it. – tbleckert Nov 25 '10 at 13:35
  • 1
    In that case, you might be interested in looking at a Tutorialzine article from June, [http://tutorialzine.com/2010/06/apple-like-retina-effect-jquery-css/](Apple-like Retina Effect With jQuery). It covers getting a working effect like that, though with a static image rather than a ` – Chris Morgan Nov 25 '10 at 13:54
  • I actually ended up needing to use CSS border-radius as an alternative to the answer which I posted above that was marked as the accepted answer in the project I've been working on for Samsung's Olympic Genome Project facebook app. It turned out to be the best solution for doing what we needed to do to make the recent release of Chrome 18 not completely ruin the performance of the app, since they made 2D Canvas hardware acceleration enabled by default in that version, which had a bug that for some reason made drawing large amounts of complex paths and images extremely slow. – jaredwilli May 14 '12 at 18:35