Reputation: 1597
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.
Upvotes: 1
Views: 137
Reputation: 13309
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, i.e. 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
Upvotes: 0
Reputation: 54069
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>
Upvotes: 1