简体   繁体   中英

How to style the remaining space in Canvas

I have a canvas with an image filling it end to end. I would like to style it and highlight focus on the face area.

Below is what I would like to achieve.

在此处输入图像描述

Here is what I have so far:

在此处输入图像描述

Note the face area should be transparent and the rest blurred.

Here is my code:

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

Note: The drawROI is where everything happens. The canvas already has an image, then I draw the oval and then the rectangle.My idea was to push the rectangle at the back and have the face being displayed in the oval clearly.

How can I achieve a similar UI as show in image 1 above.

Thanks.

I assume this is real-time. You will need to create a 2 canvases to help with the FX.

The frosted glass is on one layer. To avoid setting the blur filter overhead the filter is left on at all times.

The second layer is the inset window. An ellipse is draw and then the image over that using composite operation "source-in" (only pixels set get changed)

The final step draws the two layers onto the canvas and then adds the border and highlights as ellipses.

The demo has a random image and animates its position (just to check performance as blur can be costly)

 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>

It can be done by applying all needed effects on the entire image, and then drawing required part of the clear image on top of all the effects.

Given an original image:

原来的

Canvas filter property can be used to produce all kinds of effects. For example here, we enable blur and sepia filters and after that when drawing an image, it will have these effects applied.

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

过滤器

Then, we need to clear a shape (in your case it is an ellipse ) from this image, so it can later be filled with a unfiltered clear image. It can be done by using globalCompositeOperation canvas property - it allows to compose different sources into a final drawing.

destination-out value has the following behavior - the existing content is kept where it doesn't overlap the new shape, ie when we draw a shape with this mode on, the filtered image will remain the same, but the shape will be cleared.

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

清晰的形状

Finally we can use another composite operation to fill this empty shape with a clear image.

destination-atop will draw new clear image "behind" filtered image, but because we have a "hole" in filtered image, it will show up exactly where we need it.

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

最后

See JSBin for full source: https://jsbin.com/socexefawu/edit?html,js,output

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM