Canvas transparency mask effect reset - html

I'm trying to overlay a canvas on a page in an attempt to make it look like it has been frosted over and then use mousemove to clear it away, following this post:
http://www.stevecalderon.com/2012/03/html5-canvas-tricks-transparency-mask-for-eraser-effect.html
I have created a jsfiddle - http://jsfiddle.net/ZWsG6/2/
var canvas = "";
var ctx = "";
function frost2(){
var int=self.setInterval(function(){clock()},1000);
canvas = document.createElement("canvas");
canvas.width = window.innerWidth;
canvas.height= window.innerHeight;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
var objTo = document.getElementById("fltouch");
objTo.appendChild(canvas);
ctx = canvas.getContext('2d');
ctx.globalAlpha = 0.95;
ctx.fillStyle = "rgb(0, 255, 255)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'destination-out';
function drawPoint(pointX,pointY){
///scrape
var grd = ctx.createLinearGradient(pointX - 75,pointY-5,pointX + 75,pointY + 5)
grd.addColorStop(0, "transparent");
grd.addColorStop(0.3, "rgba(255,255,255,.6)");
grd.addColorStop(0.7, "rgba(255,255,255,.6)");
grd.addColorStop(1, "transparent");
ctx.fillStyle = grd;
ctx.fillRect(pointX - 75,pointY,400,30);
}
canvas.addEventListener('mousemove',function(e){
e.preventDefault();
drawPoint(e.pageX,e.pageY);
},false);
}
function clock(){
ctx.globalAlpha = 0.95;
ctx.fillStyle = "rgb(0, 255, 255)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'destination-out';
}
I've added a function clock() to run every second and refill the canvas with the colour so that it needs to be cleared again but it doesn't work, only remove the fill altogether.
Can anyone please tell how to reset the canvas to be filled with colour again? If it could be done slowly to look sorta like it is freezing over again that would be a bonus.

You need to set globalCompositeOperation back to default source-over value before refilling a canvas:
function clock() {
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 0.95;
ctx.fillStyle = "rgb(0, 255, 255)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'destination-out';
}
http://jsfiddle.net/VqCbv/1/

Related

HTML canvas: How to achieve dropshadow on clipped object?

I am trying to draw a dropshadow on a clipped image.
function drawCircle(ctx,w,h) {
ctx.beginPath();
ctx.ellipse(w/2, h/2, w/2, h/2, 0, 2 * Math.PI, false);
//ctx.fill(); // This helps, but not suitable for transparent images!
}
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = () => {
// Draw ellipse & clip (removing these 2 lines makes the dropshadow appear)
drawCircle(ctx, img.width,img.height);
ctx.clip();
ctx.shadowOffsetX = 4;
ctx.shadowOffsetY = 4;
ctx.shadowBlur = 4;
ctx.shadowColor = '#000';
ctx.clip();
ctx.drawImage(img, 0, 0);
}
img.src = 'https://i.imgur.com/NdrmFsr.jpg';
Without clipping, the image on the canvas has a very visible drop shadow.
With clipping applied, the drop shadow is not visible.
What do I need to change to make the drop shadow visible on a clipped image?
Fiddle is here: https://jsfiddle.net/av01d/xdemLs2u/
Using the css property filter
filter: drop-shadow(10px 6px 3px rgba(50, 50, 0, 1));
https://jsfiddle.net/nj2rpxz6/
Here is the mozzila doc about the drop-shadow() CSS function
Edit :
Here is how you can add css with Javascript :
const canvas = document.getElementById('c');
canvas.style.filter = "drop-shadow(10px 6px 3px rgba(50, 50, 0, 1))"
I found a solution. Draw the clipped object on a temporary canvas, then draw that canvas with shadow applied to the main canvas.
function drawCircle(ctx,w,h) {
ctx.beginPath();
ctx.ellipse(w/2, h/2, w/2, h/2, 0, 2 * Math.PI, false);
}
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var img = new Image();
img.onload = () => {
ctx.save();
drawCircle(ctx, img.width,img.height);
ctx.clip();
ctx.drawImage(img, 0, 0);
ctx.restore();
const canvas2 = document.createElement('canvas'), ctx2 = canvas2.getContext('2d');
canvas2.width = canvas.width; canvas2.height = canvas.height;
ctx2.shadowOffsetX = 4;
ctx2.shadowOffsetY = 4;
ctx2.shadowBlur = 4;
ctx2.shadowColor = '#000';
ctx2.drawImage(canvas,0,0);
//ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(canvas2,0,0);
}
img.src = 'https://i.imgur.com/NdrmFsr.jpg';

Chrome Canvas: Small globalAlpha ignored

On Chromium 90.0.4430.93 (which uses the same rendering engine as Chrome), the following fading effect does not work. Why? And how would one fix it?
It is done by painting over the canvas with another black canvas and a low globalAlpha on each requestAnimationFrame call.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
let start;
let lastTime;
function draw() {
canvas.bgcanvas = document.createElement('canvas');
canvas.bgcanvas.width = canvas.width;
canvas.bgcanvas.height = canvas.height;
bgctx = canvas.bgcanvas.getContext('2d');
bgctx.fillStyle = '#000';
bgctx.fillRect(0, 0, canvas.width, canvas.height);
}
function step(timestamp) {
if (start === undefined)
start = timestamp;
lastTime = timestamp;
const elapsed = timestamp - lastTime;
lastTime = timestamp;
ctx.globalAlpha = 0.0001*elapsed;
ctx.drawImage(canvas.bgcanvas, 0, 0);
ctx.globalAlpha = 1;
var x = (timestamp - start)*0.01 % 200;
var y = 0;
ctx.beginPath();
ctx.fillStyle = '#77f';
ctx.arc(x, canvas.height/2-y, 7, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
window.requestAnimationFrame(step);
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
lines = [];
draw();
ctx.drawImage(canvas.bgcanvas, 0, 0);
}
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();
window.requestAnimationFrame(step);
* { margin:0; padding:0; } /* to remove the top and left whitespace */
body {
width:100%; height:100%;
}
canvas {
display:block;
}
<canvas id="canvas"></canvas>
Result in Firefox:
Result in Chromium:
Thanks to Blindman67's comment, I now understand that it is non-trivial to get the same exponential decay shown in the above figure. But a linear one that works browser independently is relatively simple: Just subtract #010101 on each rendering step via the following code:
ctx.globalCompositeOperation = "difference";
ctx.fillStyle = '#010101';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "source-over";
To change the speed of decay, one can e.g. only apply this effect every i-th iteration.
Since I needed this fade with a background that is not pure black, but has other content, I created a luminosity mask on a new canvas (contentcanvas) that I then applied to the background. Here is an example showcasing the full solution:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
let start;
let lastTime;
let stepMod = 0;
function draw() {
canvas.bgcanvas = document.createElement('canvas');
canvas.bgcanvas.width = canvas.width;
canvas.bgcanvas.height = canvas.height;
bgctx = canvas.bgcanvas.getContext('2d');
canvas.contentcanvas = document.createElement('canvas');
canvas.contentcanvas.width = canvas.width;
canvas.contentcanvas.height = canvas.height;
canvas.contentctx = canvas.contentcanvas.getContext('2d');
canvas.tmpcanvas = document.createElement('canvas');
canvas.tmpcanvas.width = canvas.width;
canvas.tmpcanvas.height = canvas.height;
canvas.tmpctx = canvas.tmpcanvas.getContext('2d');
bgctx.fillStyle = '#000';
bgctx.fillRect(0, 0, canvas.width, canvas.height);
bgctx.font = "30px serif";
bgctx.fillStyle = '#ca3';
bgctx.fillText("Hey!", 10, canvas.height/2+10);
}
function step(timestamp) {
if (start === undefined)
start = timestamp;
lastTime = timestamp;
const elapsed = timestamp - lastTime;
lastTime = timestamp;
if (stepMod == 1) {
canvas.contentctx.globalCompositeOperation = "difference";
canvas.contentctx.fillStyle = '#010101';
canvas.contentctx.fillRect(0, 0, canvas.width, canvas.height);
canvas.contentctx.globalCompositeOperation = "source-over";
stepMod = 0;
}
else {
stepMod += 1;
}
var x = (timestamp - start)*0.01 % 200;
var y = 0;
canvas.contentctx.beginPath();
canvas.contentctx.fillStyle = '#fff';
canvas.contentctx.arc(x, canvas.height/2-y, 7, 0, Math.PI * 2, true);
canvas.contentctx.closePath();
canvas.contentctx.fill();
canvas.tmpctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.tmpctx.drawImage(canvas.contentcanvas, 0, 0);
canvas.tmpctx.globalCompositeOperation = "multiply";
canvas.tmpctx.fillStyle = '#77f';
canvas.tmpctx.fillRect(0, 0, canvas.width, canvas.height);
canvas.tmpctx.globalCompositeOperation = "source-over";
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "difference";
ctx.drawImage(canvas.contentcanvas, 0, 0);
ctx.globalCompositeOperation = "multiply";
ctx.drawImage(canvas.bgcanvas, 0, 0);
ctx.globalCompositeOperation = "lighter";
ctx.drawImage(canvas.tmpcanvas, 0, 0);
ctx.globalCompositeOperation = "source-over";
window.requestAnimationFrame(step);
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
lines = [];
draw();
ctx.drawImage(canvas.bgcanvas, 0, 0);
}
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();
window.requestAnimationFrame(step);
* { margin:0; padding:0; } /* to remove the top and left whitespace */
body {
width:100%; height:100%;
}
canvas {
display:block;
}
<canvas id="canvas"></canvas>

What are the proper way to completly clear HTML5 canvas after globalComposite operation?

I know this Q/A may be related but using the solutions there I can't clear my canvas completely.
After I set the globalCompositeOperation of my context, my canvas still had some layer after calling clearRect().
The canvas.width = canvas.width solution will do the trick, but there says it is not supported in all browsers and also a bad practice.
How do we properly clear a canvas after globalComposite operation?
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
const reader = new FileReader();
const filterRed = document.getElementById("red");
const addText = document.getElementById("addText");
const upload = document.getElementById("upload");
upload.addEventListener("change", function(evnt) {
const file = evnt.target.files[0];
reader.onload = function(e) {
img.src = e.target.result;
img.onload = function() {
ctx.drawImage(img, 0, 0, canvas.clientWidth, canvas.height);
};
};
reader.readAsDataURL(file);
});
filterRed.addEventListener("click", function(e) {
// redraw image again to prevent double clicking
// behave not normaly
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
ctx.fillStyle = "red";
ctx.filter = "contrast(0.6) brightness(100%)";
ctx.globalCompositeOperation = "multiply";
ctx.fillRect(0, 0, canvas.width, canvas.height);
});
addText.addEventListener("click", function(e) {
// redraw all things again
// but here not behave normaly
canvas.toBlob(function(blob) {
img.src = URL.createObjectURL(blob);
img.onload = function() {
// using canvas.width = canvas.width solve the problem
// but it is not the right way
canvas.width = canvas.width;
ctx.drawImage(img, 0, 0, canvas.width, canvas.width);
ctx.font = "50px serif";
ctx.fillStyle = "#00ff00";
ctx.fillText("Hello world", 50, 90);
};
});
});
<p>
Upload Image First
</p>
<label for="upload">Click to select file</label>
<input type="file" accept="image/*|video/*" id="upload" hidden>
<canvas id="canvas"></canvas>
<button id="red">
red
</button>
<button id="addText">
redraw with text
</button>
Here the reproduction codesandbox
The image should not have a white layer on top of it, the text should be white not the image.
You have to reinitialize your context globalCompositeOperation to its default value: source-over.
Note that you may also need to reset the context transformation matrix to its default, this can be done by using setTransform method and an identity matrix 1, 0, 0, 1, 0, 0.
The only thing that may now block our clearRect from working correctly would be an active clipping rule. So when you use clip() be sure that you always did call ctx.save() before, and then call ctx.restore() afterward to disable the clipping region (or don't use clip at all).
const ctx = canvas.getContext('2d');
draw_btn.onclick = drawStuffs;
simpleclear_btn.onclick = brokenClear;
better_btn.onclick = betterClear;
drawStuffs();
function drawStuffs() {
ctx.fillStyle = 'rgba(0,34,123, .75)';
ctx.fillRect(0,0,45,45);
ctx.globalCompositeOperation = 'source-atop';
ctx.fillStyle = "red";
ctx.translate(25, 25);
ctx.fillRect(0,0,45,45);
}
function brokenClear() {
ctx.clearRect(0,0,canvas.width,canvas.height);
}
function betterClear() {
ctx.globalCompositeOperation = 'source-over';
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,canvas.width,canvas.height);
}
<button id="draw_btn">draw stuff</button>
<button id="simpleclear_btn">simple clear (broken)</button>
<button id="better_btn">better clear</button>
<canvas id="canvas"></canvas>

Draw rectancle with border without filling in html5

I am trying to create a grid in the canvas as below
var canvas = $("#canvas");
var ctx = canvas.get(0).getContext("2d");
ctx.strokeStyle = "rgb(0, 0, 0)";
for(x=0;x<=300;x++){
for(y=0;y<=300;y++){
ctx.strokeRect(x, y, 20, 20);
}
}
but it just fills the HTML5 canvas instead of drawing squares with border. Can someone enlighten me on what I'm doing wrong here?
I re-wrote it like this
var canvas = $("#myCanvas");
var context = canvas.get(0).getContext("2d");
context.strokeStyle = "rgb(255, 0, 0)";
for(x=0; x<5; x++){
for(y=0; y<5; y++){
context.strokeRect(y*20, x*20, 20, 20);
}
}

Canvas shapes becoming aliased when re-drawn in safari

I'm drawing a simple progress indicator using canvas. When the element is drawn for the first time it looks all nice and anti-aliased, but when drawn a second time, it loses it's anti-aliasing. Anyone know what could be going on here?
function drawProgress(id, percent) {
var selected = $(safeID(id)).is('.selected');
var canvas = $(safeID("CANVAS_" + id));
var ctx = $(canvas)[0].getContext('2d');
ctx.clearRect();
if ( selected ) {
ctx.fillStyle = "#ffffff";
ctx.strokeStyle = "#ffffff";
}
else {
ctx.fillStyle = "#99a7ca";
ctx.strokeStyle = "#99a7ca";
}
ctx.beginPath();
ctx.arc(canvas.width()/2.0, canvas.height()/2.0, canvas.width()/2.0-1, 0, Math.PI, false);
ctx.fill();
ctx.beginPath();
ctx.arc(canvas.width()/2.0, canvas.height()/2.0, canvas.width()/2.0-1, 0, Math.PI*2.0, false);
ctx.stroke();
}
You need to specify dimensions to clearRect.