HTML5画布 - 用图像填充圆形

35

我该如何在圆形内绘制一张图片?如果我这样做:

context.beginPath();
context.arc((e.pageX),(e.pageY),161,0,Math.PI*2,true);
context.closePath();
怎样使用fill()方法将绘制的图像填充到画布上?
4个回答

67

前几天我为我正在制作的一个大项目做了这件事;

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();
};

对我来说完美地工作了。

这是我制作的更复杂版本,还包括图像缓存:https://jsfiddle.net/jaredwilli/ex5n5/


2
虽然这最终是我在画布中提出的解决方案,但在对所有需要为我所开发的应用程序执行的各种操作进行高度增强和修改后,结果在启动前2天发现有问题,必须要做另一个替代方案,结果它比原来的更快、更跨浏览器,适用于桌面和移动设备。解决方案是@MatTheCat建议的“使用带有CSS的<img>进行边界半径”。只需使用border-radius:100%使其成为圆形即可。迄今为止最佳的解决方案。就是这样... - jaredwilli
只是想补充一下,最佳实践是在设置onload回调之后再设置src。也就是说,thumbImg.src = 'path_to_image';应该成为最后一行。 - Raul Pinto
确实最好在onload之后设置src。然而,我发现即使你像我一样快速地运行数百张图片,让它们在画布上绘制时不显示破碎的图像,除非你像我一样使用它,否则这并没有什么大的区别。然而,在那种情况下,最好也使用deferreds,这也是我最终采取的方法。 - jaredwilli
请问您能否为这个 tmpCtx 添加 jsfiddle?因为我发现它对我来说是未定义的。 - Pravin W
@PravinWaychal 这是我很久以前发布这个答案时制作的一个小工具。那时候,这是我需要完成的相当复杂的任务的一种实现方式。但你应该能够理解其中的思路。 https://jsfiddle.net/jaredwilli/ex5n5/ - jaredwilli

14

不确定您是否仍在寻找答案,但以下是方法:

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";

你也可以使用pattern来实现这个,但是会失去一些图片摆放的灵活性。

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"

5
考虑使用以下替代方案:
  • 使用带有CSS的<img>来实现border-radiushttp://jsfiddle.net/ChrisMorgan/BQGxA/

  • 使用SVG而不是<canvas>,并将椭圆形设置为图像的剪切路径(更复杂的剪切路径也很容易实现)

我不知道您的要求和情况,所以不确定这些是否能满足您的需求,但我认为值得考虑。对于许多情况,<canvas>并不是解决所有问题的方案 - 在这些情况下,普通HTML中的CSS和/或SVG可能更加匹配。


SVG可能是正确的选择。我的做法是,当我暂停视频时,会弹出一个放大镜。然后使用画布在画布上绘制视频图像并进行缩放。问题是放大镜是圆形的,所以我需要将其剪辑或用它填充一个弧形。 - tbleckert
1
在这种情况下,您可能会对查看Tutorialzine 6月份的一篇文章感兴趣,[http://tutorialzine.com/2010/06/apple-like-retina-effect-jquery-css/](使用jQuery实现类似苹果Retina效果)。它介绍了如何获得类似的工作效果,但是使用的是静态图像而不是`<video>`帧。 - Chris Morgan
实际上,在我为三星奥林匹克基因组项目Facebook应用程序工作的项目中,我最终需要使用CSS border-radius作为替代我之前发布的被标记为接受答案的答案。结果证明,这是解决我们需要做的事情的最佳方案,以使Chrome 18的最新版本不会完全破坏应用程序的性能,因为他们默认启用了2D Canvas硬件加速,但该版本存在一个错误,导致绘制大量复杂路径和图像非常缓慢。 - jaredwilli

5
使用clip()方法的问题在于Chrome将呈现非平滑边框,如此问题所示。其中一个解决方案是使用globalCompositeOperation,正如Daniel的答案中所示:
//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, ...);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接