How can I clear an arc or circle in HTML5 canvas? - javascript

I found that there's a clearRect() method, but can't find any to clear an arc (or a full circle).
Is there any way to clear an arc in canvas?

There is no clearArc however you can use Composite Operations to achieve the same thing
context.globalCompositeOperation = 'destination-out'
According to MDC the effect of this setting is
The existing content is kept where it doesn't overlap the new shape.
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Compositing
So any filled shape with this mode on will end up erasing current canvas content.

This is a circular equivalent of clearRect(). The main thing is setting up a composite operation per #moogoo's answer.
var cutCircle = function(context, x, y, radius){
context.globalCompositeOperation = 'destination-out'
context.arc(x, y, radius, 0, Math.PI*2, true);
context.fill();
}
See https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html:

Nope, once you've drawn something on a canvas there is no object to clear, just the pixels you've drawn. The clearRect method doesn't clear a previously drawn object, it just clears the pixels in the space defined by the parameters. You can use the clearRect method to clear the arc if you know a rectangle which contains it. This will of course clear any other pixels in the area, so you'll have to redraw them.
Edit: MooGoo has given a much better answer below

You can use the clearRect() method to erase a portion of the canvas (including your arc), but when you're using clearRect() with arcs or anything else that you used beginPath() and closePath() for while drawing, you'll need to handle the paths while erasing, too. Otherwise, you may end up with a faded version of your arc still appearing.
//draw an arc (in this case, a circle)
context.moveTo(x, y);
context.beginPath();
context.arc(x,y,radius,0,Math.PI*2,false);
context.closePath();
context.strokeStyle = "#ccc";
context.stroke();
//now, erase the arc by clearing a rectangle that's slightly larger than the arc
context.beginPath();
context.clearRect(x - radius - 1, y - radius - 1, radius * 2 + 2, radius * 2 + 2);
context.closePath();

Make sure to call beginPath()
function animate (){
requestAnimationFrame(animate)
c.clearRect(0,0,canvas.width,canvas.height);
c.beginPath();
c.arc(x,y,40,0,Math.PI * 2,false);
c.strokeStyle='rgba(200,0,0,1)';
c.stroke();
c.fillStyle ='rgba(0,0,0,1)';
c.fillRect(x,y,100,100);
x++
} animate()
Credit to #Gabriele Petrioli in this answer: Why doesn't context.clearRect() work inside requestAnimationFrame loop?

Here's an updated fiddle for you too (uses clearRect): https://jsfiddle.net/x9ztn3vs/2/
It has a clearApple function:
block.prototype.clearApple = function() {
ctx.beginPath();
ctx.clearRect(this.x - 6, this.y - 6, 2 * Math.PI, 2 * Math.PI);
ctx.closePath();
}

Related

Why does javascript canvas2d clipping require a path?

I was pulling my hair out over this bug for a while. I wanted to render images in three sections of a canvas, without allowing them to overlap. Basically, I wanted to use canvas.getContext('2d').clip() to keep the images separated. However, the clip only works if I call canvas.getContext('2d').beginPath() after I draw the image.
So this does not work (no clip is applied):
this.draw=function(image, cx, cy, width, height, clip){
var ctx = this.canvas.getContext('2d');
ctx.save();
ctx.rect(clip.x, clip.y, clip.width, clip.height);
ctx.clip();
ctx.fillStyle = "black";
ctx.fillRect(clip.x, clip.y, clip.width, clip.height);
ctx.drawImage(image,cx-width/2,cy-height/2,width,height);
ctx.restore();
return this;
};
But this does:
this.draw=function(image, cx, cy, width, height, clip){
var ctx = this.canvas.getContext('2d');
ctx.save();
ctx.rect(clip.x, clip.y, clip.width, clip.height);
ctx.clip();
ctx.fillStyle = "black";
ctx.fillRect(clip.x, clip.y, clip.width, clip.height);
ctx.drawImage(image,cx-width/2, cy-height/2,width,height);
ctx.beginPath();// <------WITCHCRAFT
ctx.restore();
return this;
};
It was a total accident that I discovered that beginPath() fixes the problem, and I have no idea why. Can anyone explain this to me?
Because clipping requires a path? Perhaps you missed it in the documentation. Here's what MDN documentation says:
The CanvasRenderingContext2D.clip() method of the Canvas 2D API turns the path currently being built into the current clipping path.
(emphasis mine)
The reason it needs a path is because clipping mask can be any arbitrary shape from rectangles to circles to the outline of Pikachu.
For the sake of completeness, here's what the W3C spec says about .clip():
https://www.w3.org/TR/2dcontext/#drawing-paths-to-the-canvas
context . clip()
Further constrains the clipping region to the current path.

Canvas clipping region and the canvas stack

I have started working through the O'Reilly book "HTML5 Canvas." I'm in the second chapter, and one of the examples presents code which is not very well explained. Example 2-5:
draw a black box
push state
set small clipping region in top left
draw circle
pop state
set large clipping region
draw another circle
But I'm having trouble understanding some things:
context.fillStyle = 'black';
context.fillRect(10, 10, 200, 200);
context.save();
context.beginPath();
context.rect(0, 0, 50, 50);
context.clip();
context.beginPath();
context.strokeStyle = 'red';
context.lineWidth = 5;
context.arc(100, 100, 100, 0, 2*Math.PI, false);
context.stroke();
context.closePath();
context.restore();
context.beginPath();
context.rect(0, 0, 500, 500);
context.clip();
context.beginPath();
context.strokeStyle = 'blue';
context.lineWidth = 5;
context.arc(100, 100, 50, 0, 2*Math.PI, false);
context.stroke();
context.closePath();
My questions:
First, does context.clip() implicitly close the context path ("context.closePath()")? It is preceded by a context.beginPath(), and followed by another context.beginPath(). Like this:
context.beginPath();
context.rect(0, 0, 50, 50);
context.clip();
context.beginPath();
Second, why is it necessary to push the context state? Why can't I just change the clipping region? It seems to be necessary, because it doesn't work without pushing the state. If I don't push the state and then restore it, the big blue circle does not show up, and I don't understand why.
Does context.clip() implicitly close the context path ?...
It is preceded by a context.beginPath(), and followed by another context.beginPath(). Like this: [...]
Yes, this is only way to create a close shape which is required for clipping so if a closePath() isn't called clip() will close the path internally.
The specification states:
Open subpaths must be implicitly closed when computing the clipping region, without affecting the actual subpaths.
beginPath() will clear the current main path and all its sub-paths. The clipping still resides active though, but now you can do other path operations which will be affected by the clip region when rasterized.
Why is it necessary to push the context state?
There is no way to reset a clip region although it has been suggested and discussed (there is a resetClip() in the standard but not yet widely supported). Calling clip() several times -
The clip() method must create a new clipping region by calculating the intersection of the current clipping region [...]
In other words, it won't be replaced if we say defined a clip region for the entire draw surface.
So the only way we can remove a clip is to save the state, set clip then restore to remove it.

HTML5 Canvas - Redrawing new circles after erasing them with clip

I have a unique problem.
I am creating a game of snake with HTML5 and Canvas
I have a function that generates apples on the board randomly and removes them after a set period of time. In order to remove circles, you have to use the clip() function followed by clearRect().
However, after you use the clip function, you can no longer draw new circles.
The solution I found was using ctx.save() and ctx.restore(). However, if you play the game, you will learn that the snake acts crazy when circles disappear and new circles appear.
I suspect this has to do with my use of the save and restore functions.
Here's the specific code in question
var width = canvas.width;
var height = canvas.height;
var applesArray = []; // Store the coordinates generated randomly
// Generates a random coordinate within the confines of the canvas and pushes it to the apples array
function randCoord() {
var coord = Math.floor(Math.random() * height);
applesArray.push(coord);
return coord;
}
function generateApples() {
ctx.beginPath();
ctx.fillStyle = "green";
ctx.arc(randCoord(),randCoord(),3,0, 2 * Math.PI);
ctx.fill();
ctx.save(); // To redraw circles after clip, we must use save
ctx.clip(); // Allows only the circle region to be erased
setTimeout(function() {
ctx.clearRect(0, 0, width, height);
},3000);
ctx.restore(); // We must restore the previous state.
}
setInterval(function() {
generateApples();
},4000);
You can play the game here
https://jsfiddle.net/2q1svfod/9/
Can anyone explain this weird behavior? I did not see it coming?
The code has multiple issues.
The code that draws the snake (e.g. upArrow function) simply extends the current path. This is a problem because the code that draws the apple starts a new path. Note that save/restore in apple drawing code does not help because path is not part of the state that is saved/restored. The code that draws the snake will need to start a new path. For example...
function upArrow() {
if (direction == "up") {
goUp = setInterval(function() {
ctx.beginPath();
ctx.moveTo(headX, headY);
ctx.lineTo(headX, headY - 10);
ctx.stroke();
headY -= 10;
}, 400);
}
}
The save/clip/restore calls are in the code that draws the apple. These methods need to be moved into the timeout callback function that erases the apple. Also, the code that erases the apple will need to recreate the path (because the snake drawing could have changed the path between when the apple is drawn and when the apple is erased). For example...
function generateApples() {
var cx = randCoord();
var cy = randCoord();
ctx.beginPath();
ctx.fillStyle = "green";
ctx.arc(cx, cy,3,0, 2 * Math.PI);
ctx.fill();
setTimeout(function() {
ctx.beginPath();
ctx.arc(cx,cy,3,0, 2 * Math.PI);
ctx.save();
ctx.clip();
ctx.clearRect(0, 0, width, height);
ctx.restore();
},40000);
}
These changes will get you close to what you intended. However, there will still be a couple minor issues.
When drawing the apple, there will be some anti-aliasing occuring around the edge of the apple's path. The clear operation can miss clearing some of these pixels. After the clear operation, you might see a semi-transparent outline of where the apple was. You could work around this issue by using a slightly larger circle radius when clearing the apple.
Another issue is that apples could be drawn on top of the snake. Erasing the apple will also erase the snake. There is not an easy fix for this issue. You would need to store all the coordinates for the snake and then redraw all or part of the snake.
In the long term, you may want to consider the suggestions in the comments about restructuring your logic to track all objects and redraw everything each frame (or redraw everything after each change).

how can we take advantage of beginPath() canvas method?

please I need your help I am new to HTML5 and I am a little confused , I have written the following code in my editor
var canvas = document.getElementById('paper');
var c= canvas.getContext('2d');
c.beginPath();
c.lineWidth="5";
c.moveTo(0,75);
c.lineTo(250,75);
c.stroke(); // Draw it
c.beginPath();
c.lineWidth="5";
c.moveTo(0,0);
c.lineTo(250,75);
c.stroke(); // Draw it
but when I removed the second c.beginPath() , no thing changed!!!
so how can we take the advantage of beginPath() method.
can any one explain for me using clear example.
thank you so much every body.
Here's the solution to the mystery :
• beginPath creates a new path.
• moveTo creates a new sub-path within the current path.
So when using two times beginPath, you are drawing two lines.
When using beginPath only once, you draw one single figure that contains two sub-path that are lines.
The principle of sub-path allows you to build whatever you want to fill/stroke as you want, then stroke all those sub-path at once.
You can use the way you prefer.
About style : when using fill or stroke, the current path will be drawn in the current style ( fillStyle / strokeStyle / lineWidth / font / ...).
So you are obliged, to draw with a different style, to create a new path with beginPath.
On the other hand, if you are drawing a lot of figures with the same style, it is more logical to set the style once, create all the sub-paths, and fill/stroke everything.
Rq : it is a good habit when drawing to :
1) set your style
2) build your path / sub-paths,
3) then fill and/or stroke.
Because mixing styles, paths and strokes/fills, will just confuse things.
Edit : When you come to more complex drawings, you have to change also the transform : you scale, rotate and translate.
It can become quite hard to know the current status of the canvas.
In fact, even when dealing only with regular style only, it might be difficult to both avoid setting everything on each draw AND know what is the current setting.
The solution is to save the context before your draw, and restore it afterwise :
1) save context
2) set style
3) set transform
4) build path / sub- path
5) restore context.
here's a simple-but-not-too simple example :
function drawSmile(ctx, x, y, faceRadius, eyeRadius) {
ctx.save(); // save
ctx.fillStyle = '#FF6'; // face style : fill color is yellow
ctx.translate(x, y); // now (x,y) is the (0,0) of the canvas.
ctx.beginPath(); // path for the face
ctx.arc(0, 0, faceRadius, 0, 6.28);
ctx.fill();
ctx.fillStyle = '#000'; // eye style : fill color is black
ctx.beginPath(); // path for the two eyes
ctx.arc(faceRadius / 2, - faceRadius /3, eyeRadius, 0, 6.28);
ctx.moveTo(-faceRadius / 2, - faceRadius / 3); // sub path for second eye
ctx.arc(-faceRadius / 2, - faceRadius / 3, eyeRadius, 0, 6.28);
ctx.fill();
ctx.restore(); // context is just like before entering drawSmile now.
}
drawSmile(ctx, 100, 100, 60, 12);
.
For the record, code to draw Two lines :
c.lineWidth="5";
c.beginPath(); // new path
c.moveTo(0,75);
c.lineTo(250,75);
c.stroke(); // Draw it
c.lineWidth="10";
c.beginPath(); // new path
c.moveTo(0,0);
c.lineTo(250,75);
c.stroke(); // Draw it
One path having two lines as sub-paths :
c.lineWidth="5";
c.beginPath(); // new path
c.moveTo(0,75);
c.lineTo(250,75);
c.moveTo(0,0); // new sub-path within current path
c.lineTo(250,75);
c.stroke(); // Draw the two lines at once.

How do I clip a image obtained from the getImageData() function?

I'm trying to get the contents of a source canvas, clip it, and then draw it on another canvas. Even though my code works like a charm using a src PNG / new Image() combo, it does not when the source content comes from another canvas.
the code is:
var imgData = src_ctx.getImageData(x, y, w, h);
dest_ctx.putImageData(imgData, x, y+h);
ctx.beginPath(); // Filled triangle
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.lineTo(x2,0);
ctx.lineTo(x1,0);
ctx.clip();
After defining the clipping region, draw the source canvas using drawImage, instead of setting the imagedata.
dest_ctx.beginPath(); // Filled triangle
dest_ctx.moveTo(x1,y1);
dest_ctx.lineTo(x2,y2);
dest_ctx.lineTo(x2,0);
dest_ctx.lineTo(x1,0);
dest_ctx.clip();
// You can control wich region to draw using all the arguments
// drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
dest_ctx.drawImage (srcCanvas, x, y);
getImageData is an almost useless function unless you know what you're doing (ie. checking for hit detection, filtering pixels) but even then it is painfully slow.
I created a JSfiddle example for you fiddle around with (see what I did there!)
The heart of the code is as follows:
1 canvas = document.getElementById('canvas');
2 ctx = canvas.getContext("2d");
3 _canvas=document.createElement('canvas');
4 _ctx = _canvas.getContext("2d");
5 _canvas.width = 200;
6 _canvas.height = 200;
7
8 _ctx.beginPath();
9 _ctx.arc(100, 100, 100,0,Math.PI*2,true);
10 _ctx.clip();
11 _ctx.drawImage(img1, 0, 0);
12
13 ctx.drawImage(_canvas, 1.25 * i * _canvas.width, 500);
Essentially what you are doing is clipping to a cache canvas (_canvas, lines 10 and 11) and drawing that to the main canvas (canvas, line 13).
Note: Ideally you would translate your image so it would be in the center of the clip, but I still can not get my head around translations, especially when coupled with other transformations such as clips.

Categories

Resources