【问题标题】:How to style the remaining space in Canvas如何在 Canvas 中设置剩余空间的样式
【发布时间】:2020-07-16 12:06:45
【问题描述】:

我有一个画布,里面有一张端到端的图片。我想对其进行样式设置并突出显示面部区域的焦点。

以下是我想要实现的目标。

这是我目前所拥有的:

注意面部区域应该是透明的,其余部分是模糊的。

这是我的代码:

var ctx = context.drawImage(, 0, 0, 500, 500);
drawROI(ctx, width / 4, 50, 250, 350);

drawROI(ctx, x, y, w, h) {
  var kappa = 0.5522848,
    ox = (w / 2) * kappa,
    oy = (h / 2) * kappa,
    xe = x + w,
    ye = y + h,
    xm = x + w / 2,
    ym = y + h / 2;

  // Draw Oval
  ctx.beginPath();
  ctx.moveTo(x, ym);
  ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  ctx.closePath();
  ctx.lineWidth = 5;
  ctx.stroke();
  ctx.strokeStyle = "#999";

  // Draw Rectange
  ctx.beginPath();
  ctx.rect(0, 0, this.video.width, this.video.height);
  ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
  ctx.fill();
  ctx.stroke();
}

注意:drawROI 是一切发生的地方。画布已经有图像了,然后我画椭圆,然后画矩形。我的想法是把矩形推到后面,让脸在椭圆中清晰地显示出来。

如何实现与上图 1 所示类似的 UI。

谢谢。

【问题讨论】:

    标签: javascript css user-interface canvas html5-canvas


    【解决方案1】:

    我认为这是实时的。您将需要创建 2 个画布来帮助使用 FX。

    磨砂玻璃在一层。为避免在头顶设置模糊滤镜,滤镜始终处于开启状态。

    第二层是插入窗口。绘制一个椭圆,然后使用合成操作"source-in" 将图像覆盖在该椭圆上(仅更改像素集)

    最后一步将两个图层绘制到画布上,然后将边框和高光添加为椭圆。

    演示有一个随机图像并对其位置进行动画处理(只是为了检查性能,因为模糊可能代价高昂)

    const ctx = canvas.getContext("2d");
    ctx.fillText("Loading image please wait..", 10,20)
    Math.TAU = Math.PI * 2;
    const img = new Image;
    img.src = "http://www.createjs.com/demos/_assets/art/flowers.jpg";
    
    
    img.onload = () => {
    
        // settings
        const blurAmount = 12; // in pixels
        const glassBlur = "blur(" + blurAmount + "px)"; // the blur filter
        const glassColor = "#EEE";
        const glassOpacity = 0.45;
        const faceRadius2 = canvas.height * (1/4);
        const faceRadius1 = canvas.width * (1/3);
        const borderWidth = canvas.width * (1/25);
        const insetBorderColor = "#999";
        const highlightColor = "255,255,255";
    
        // background image holds frosty glass
        const bg = document.createElement("canvas");
        bg.width = canvas.width;
        bg.height = canvas.height;
        bg.ctx = bg.getContext("2d");
        bg.ctx.drawImage(img, 0, 0);
        bg.ctx.filter = glassBlur;   // IMPORTANT TO SET FILTER EARLY or will cause
                                             // slowdown is done on the fly
    
        // create the mask for the window
        const windowMask = document.createElement("canvas");
        windowMask.width = canvas.width;
        windowMask.height = canvas.height;
        windowMask.ctx = windowMask.getContext("2d");
    
        // create the gradient for the highlights
        const highlight = ctx.createLinearGradient(
            0,
            canvas.height / 2 - faceRadius1 + borderWidth,
            0,
            canvas.height / 2 + faceRadius1 - borderWidth,
        );
        highlight.addColorStop(0,  "rgba("+highlightColor +",1)");
        highlight.addColorStop(0.25,"rgba("+highlightColor +",0.4)");
        highlight.addColorStop(0.5,"rgba("+highlightColor +",0)");
        highlight.addColorStop(0.75,"rgba("+highlightColor +",0.4)");
        highlight.addColorStop(1,  "rgba("+highlightColor +",1)");
    
        ctx.lineCap = "round"; // for the highlight
    
        var x,y; //position of image for demo
    
        // animate moving image
        requestAnimationFrame(loop);
        function loop(time) {
            x = -(Math.cos(time / 2000) * 20 + 20);
            y = -(Math.sin(time / 2000) * 20 + 20);
            frosty(img);
            faceWindow(img);
            drawFace();
            requestAnimationFrame(loop);
        
        }
    
        // draws frosted glass to bg canvas
        function frosty(img) {
             const w = bg.width;
             const h = bg.height;
             bg.ctx.drawImage(img, x, y);
             bg.ctx.fillStyle = glassColor;
             bg.ctx.globalAlpha = glassOpacity;
             bg.ctx.fillRect(-blurAmount, -blurAmount, w + blurAmount * 2, h + blurAmount * 2); 
             bg.ctx.globalAlpha = 1;
    
        }
       
    
        // creates inset window
        function faceWindow(img) {
            const w = windowMask.width;
            const h = windowMask.height;
            windowMask.ctx.clearRect(0, 0, w, h);
            windowMask.ctx.beginPath();
            windowMask.ctx.ellipse(w / 2, h / 2, faceRadius1, faceRadius2, 0, 0, Math.TAU);
            windowMask.ctx.fill();
            windowMask.ctx.globalCompositeOperation = "source-in";
            windowMask.ctx.drawImage(img, x, y,); // draw face 
            windowMask.ctx.globalCompositeOperation = "source-over";
        }
    
    
        // puts it all together.
        function drawFace() {
            const w = canvas.width;
            const h = canvas.height;
            ctx.drawImage(bg, 0, 0); // draw glass
            ctx.drawImage(windowMask, 0, 0); // draw face in window
    
            // draw border
            ctx.lineWidth = borderWidth;
            ctx.strokeStyle = insetBorderColor;
    
            ctx.beginPath();
            ctx.ellipse(w / 2, h / 2, faceRadius1, faceRadius2, 0, 0, Math.TAU);
            ctx.stroke();
    
            // draw highlights
            ctx.strokeStyle = highlight;  // gradient
            ctx.globalCompositeOperation = "lighter";
            ctx.globalAlpha = 0.65;
    
            ctx.beginPath();
            ctx.ellipse(w / 2, h / 2, faceRadius1 - borderWidth * 2, faceRadius2 - borderWidth * 2, 0, 0, Math.PI / 2);
            ctx.stroke();
            
            ctx.beginPath();
            ctx.ellipse(w / 2, h / 2, faceRadius1 - borderWidth * 2, faceRadius2 - borderWidth * 2, 0, Math.PI, Math.PI * 1.5);
            ctx.stroke();
    
            ctx.globalCompositeOperation = "source-over";
            ctx.globalAlpha = 1;
        }
    }
    canvas { border: 2px solid black; }
    <canvas id="canvas" width="200" height="350"> </canvas>

    【讨论】:

    • 非常感谢您的帮助。我将完成实施并向您更新使用的速度或资源。来源是网络摄像头,我已经处理了 FX。
    【解决方案2】:

    可以通过在整个图像上应用所有需要的效果,然后在所有效果之上绘制清晰图像的所需部分来完成。

    给定一张原始图片:

    Canvasfilter 属性可以用来产生各种效果。例如这里,我们启用模糊和棕褐色滤镜,然后在绘制图像时应用这些效果。

    context.filter = 'blur(4px) sepia(1)';
    context.drawImage(image, 0, 0, 400, 400);
    context.filter = 'none';
    

    然后,我们需要从该图像中清除一个形状(在您的情况下是ellipse),以便稍后可以用未经过滤的清晰图像填充它。这可以通过使用globalCompositeOperation canvas 属性来完成 - 它允许将不同的源组合成最终的绘图。

    destination-out 值具有以下行为 - 现有内容保留在不与新形状重叠的位置,即当我们在此模式下绘制形状时,过滤后的图像将保持不变,但形状会被清除。

    context.globalCompositeOperation = 'destination-out';
    context.ellipse(200, 80, 80, 100, 0, 0, 2 * Math.PI);
    context.fill();
    

    最后我们可以使用另一个合成操作来用清晰的图像填充这个空的形状。

    destination-atop 将在过滤后的图像“后面”绘制新的清晰图像,但因为我们在过滤后的图像中有一个“洞”,它会准确地显示在我们需要的位置。

    context.globalCompositeOperation = 'destination-atop';
    context.drawImage(image, 0, 0, 400, 400);
    

    查看 JSBin 获取完整源代码:https://jsbin.com/socexefawu/edit?html,js,output

    【讨论】:

    • 感谢您的支持。我想实现这个和上面的@Blindman67 回答并衡量性能。这很酷也很容易。谢谢
    猜你喜欢
    • 2017-03-16
    • 2013-06-18
    • 2012-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-18
    相关资源
    最近更新 更多