用“羽毛"剪裁帆布边缘效应

2022-01-17 00:00:00 canvas javascript html5-canvas

我目前正在将图像绘制到 HTML5 画布上并用弧线对其进行遮罩,在绘制图像之前调用 clip() 以便仅显示弧线中的部分.我怎样才能使这条弧的边缘羽化?我通过谷歌搜索知道没有简单的方法可以简单地将羽毛"应用到用画布绘制的形状上.边缘接触弧线的图像的像素数据是什么?感谢您的帮助.

这是我的代码的相关部分:

ctx.arc(canvas.width/2, canvas.height/2, 250, 0, 6.28, false);//画圆ctx.restore();ctx.save();ctx.drawImage(背景, 0, 0,背景.宽度*比例,背景.高度*比例);ctx.clip();//调用clip方法,使下一个渲染在最后一个路径中被剪辑ctx.drawImage(img, 0, 0,img.width * 比例,img.height * 比例);ctx.closePath();ctx.restore();

更新

感谢肯的详尽回答和非常有用的代码/评论!昨晚我花了几个小时试图在我的特定用例中使用这个解决方案,但我遇到了麻烦.似乎如果我使用您描述的第二画布技术剪辑图像,我无法像使用 arc() 和 clip() 例程那样在变换上重绘它.这是我想要完成的 JS Fiddle,减去弧上的羽化,注意两个分层图像上的单击和拖动事件.

I'm currently drawing an image to an HTML5 Canvas and masking it with an arc, calling clip() before I draw the image so that only the portion that's in the arc is shown. How can I feather the edges of this arc? I know from googling around that there is no simple way to simply apply a "feather" to a shape drawn with canvas. What abut going in on the pixel data for the image where its edges touch the arc? Thanks for any help.

Here is the relevant portion of my code:

ctx.arc(canvas.width/2, canvas.height/2, 250, 0, 6.28, false);//draw the circle
ctx.restore();
ctx.save();
ctx.drawImage(background, 0, 0,
              background.width * scale, background.height * scale);
ctx.clip();//call the clip method so the next render is clipped in last path
ctx.drawImage(img, 0, 0,
              img.width * scale, img.height * scale);
ctx.closePath();
ctx.restore();

UPDATE

Thanks for the thorough answer and very helpful code/comments Ken!! I spent a few hours last night trying to work this solution in my particular use case and I'm having trouble. It seems that if I clip an image with the second-canvas technique you describe I can't redraw it on transforms the same way that I can with an arc() and clip() routine. Here's a JS Fiddle of what I'm trying to accomplis, minus the feathering on the arc, notice the click and drag events on the two layered images.

http://jsfiddle.net/g3WkN/

I tried replacing the arc() with your method, but I'm having a hard time getting that to be responsive to the transforms that happen on mouse events.

解决方案

Update 2017/7

Since this answer was given there are now a new option available in newer browsers, the filter property on the context. Just note that not all browsers currently supports it.

For browsers which do we can cut down the code as well as remove temporary canvas like this:

var ctx = demo.getContext('2d');

ctx.fillStyle = '#f90';
ctx.fillRect(0, 0, demo.width, demo.height);

clipArc(ctx, 200, 200, 150, 40);

function clipArc(ctx, x, y, r, f) {

    ctx.globalCompositeOperation = 'destination-out';

    ctx.filter = "blur(25px)";  // "feather"
    ctx.beginPath();
    ctx.arc(x, y, r, 0, 2 * Math.PI);
    ctx.fill();

    // reset comp. mode and filter
    ctx.globalCompositeOperation = 'destination-out';
    ctx.filter = "none";
}

body {background:#07c}

<canvas id="demo" width=400 height=400></canvas>

Old answer

Technique

You can achieve this by combining the following steps:

  • Use off-screen canvas
  • Use the shadow feature (the secret ingredient)
  • Use composite modes

The concept is based on having the browser make the feather internally by utilizing the blurred shadow. This is much faster than blurring in JavaScript. As we can make shadow for any object you can make complex feathered masks.

The off-screen canvas is used to draw the shadow only. We achieve this by moving the actual shape outside the canvas and then offset the shadow accordingly. The result is that shadow is drawn on the off-screen canvas while the actual shape is "invisible".

Now that we have a feathered version of our shape we can use that as a mask for composite mode. We choose destination-out to cleat where the shadow is drawn, or destination-in to invert the mask.

Example

Lets create a wrapper function that do all the steps for us

ONLINE DEMO HERE

function clipArc(ctx, x, y, r, f) { /// context, x, y, radius, feather size

    /// create off-screen temporary canvas where we draw in the shadow
    var temp = document.createElement('canvas'),
        tx = temp.getContext('2d');
      
    temp.width = ctx.canvas.width;
    temp.height = ctx.canvas.height;

    /// offset the context so shape itself is drawn outside canvas
    tx.translate(-temp.width, 0);

    /// offset the shadow to compensate, draws shadow only on canvas
    tx.shadowOffsetX = temp.width;    
    tx.shadowOffsetY = 0;

    /// black so alpha gets solid
    tx.shadowColor = '#000';

    /// "feather"
    tx.shadowBlur = f;
    
    /// draw the arc, only the shadow will be inside the context
    tx.beginPath();
    tx.arc(x, y, r, 0, 2 * Math.PI);
    tx.closePath();
    tx.fill();

    /// now punch a hole in main canvas with the blurred shadow
    ctx.save();
    ctx.globalCompositeOperation = 'destination-out';
    ctx.drawImage(temp, 0, 0);
    ctx.restore();
}

That's all there is to it.

USAGE

clipArc(context, centerX, centerY, radius, featherSize);

With demo background (see fiddle):

ctx.fillStyle = '#ffa';
ctx.fillRect(0, 0, demo.width, demo.height);

clipArc(ctx, 200, 200, 150, 40);

Result:

If you want to keep center intact just replace composite mode with destination-in.

Demo for inverted feathered mask

相关文章