Can I stroke a path so that it is not drawn on screen but converted to a new path? If so, how?
In canvas, (almost) all shapes, stroked or filled, are paths. There is no concept of a "stroke" in canvas, but there is the concept of calling stroke() on a path.
What you can do is create a path, fill it, and then stroke the very same path.
ctx.beginPath();
ctx.moveTo(50,50);
ctx.lineTo(100,100);
ctx.lineTo(25,100);
ctx.closePath();
ctx.fillStyle = 'gold'
ctx.fill();
// The path is still there, lets stroke it
ctx.lineWidth = 4;
ctx.stroke();
Live example:
http://jsfiddle.net/9SK2C/
However note that once you start a new path, that old path is lost forever. There is no built in way to save or restore paths. If you want to keep track of a path to save/restore it, you have to account for it all yourself.
You also cannot edit a path. You can add it the end of it, but there's no going back and modifying the points in a path like there is in (say) SVG. You gotta remake it from the start with the new points instead.
Related
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.
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.
I'm working on an HTML5 game. I need to draw tail lines in the canvas and check for intersections in the game, which is a Tron-style game.
I'm actually using the drawLine() function from JCanvas, but JCanvas did not provide me a way to check for line intersection, I digged in the source and found the use the ctx object, and at the end of the function I'm using, I returned the object so I can use the ctx.isPointInPath() method to achieve what I need, but is not working, is returning false everytime...
I really don't understand what a path is - will ctx.isPointInPath() return true just for the points that are set using ctx.moveTo() after ctx.beginPath()? Or will it return true for all the points that are between 2 consecutive ctx.moveTo()s that are connected using ctx.lineTo()?
What is the use of ctx.closePath()?
And what is the difference between:
{
ctx.closePath();
ctx.fill();
ctx.stroke();
}
and:
{
ctx.fill();
ctx.stroke();
ctx.closePath();
}
What is a path?
It's a series of path commands (moveTo, lineTo, arcTo, etc.) that define the boundary of a vector shape. You can then fill and/or stroke the path as desired.
What is the Use of closePath()?
Demo: http://jsfiddle.net/YrQCG/5/
// Draw a red path using closePath() in the middle
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.moveTo(50,100);
ctx.lineTo(100,150);
ctx.lineTo(150,100);
ctx.closePath();
ctx.lineTo(50,50);
ctx.stroke();
// Slide the next path over by 150 pixels
ctx.translate(150,0);
// Draw a blue path using the exact same commands, but without closePath
ctx.beginPath();
ctx.strokeStyle = 'blue';
ctx.moveTo(50,100);
ctx.lineTo(100,150);
ctx.lineTo(150,100);
//ctx.closePath();
ctx.lineTo(50,50);
ctx.stroke();
Using closePath() causes the point of the pen to move back to the start of the current subpath, drawing a line from the current point back to that starting point; the next command starts from this new point. It's useful if you want to draw a fully outlined shape without explicitly drawing the last line.
It is equivalent to calling lineTo() with the location of the first point of your current subpath, followed by moveTo() to that same point (to establish a new subpath).
Seen above, we draw a V symbol using the first moveTo and following two lineTo commands. When we call closePath on the red path it draws the horizontal bar across and causes the next line to start from the top left corner.
When we don't call closePath in the blue path the next lineTo command continues on from the last drawn point.
Note that closePath() is not necessary most of the time, unlike beginPath() which you must call each time you want to start drawing a new path. (If you don't, all the old path drawing commands are part of the next drawing.)
This is the basic representation of closed path:
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(100,0);
ctx.lineTo(100,100);
ctx.lineTo(0,100);
ctx.closePath(); // <--the image right side has this line
ctx.stroke();
The result of closePath() is that the start and the end point will be bounded.
An example here.
var context=document.getElementById("canvas").getContext("2d");
//Red Box
context.beginPath();
context.fillStyle="Red";
context.rect(10,10,50,50);
context.fill();
//Pink circle
context.beginPath();
context.lineWidth="3";
context.fillStyle="Pink";
context.arc(250,250,50,0,Math.PI*2,false);
context.fill();
context.stroke();
context.font="1.2em Verdana";
context.fillStyle="Black";
context.fillText(context.isPointInPath(35,35),35,35);
context.fillText(context.isPointInPath(250,250),250,250);
If you write without beginPath all objects detected.
How to identify objects on the canvas or to omit beginPath?
If you want to use that function you need to rebuild the path every time you want to do the test (just don't call fill or stroke).
What I do normally instead is using my own point-in-polygon test function or my own spatial data structure if there are a lot of objects and speed is important.
Note that a canvas is just a bitmap and it doesn't store the commands you use to draw on it. That is why it cannot do the check after drawing a shape and you can test only the current path.
Once you call beginPath the previous path geometry is discarded and what you have are only the affected pixels if you called fill or stroke.
May be for your case it may make sense to check the color of the canvas pixel...
I've just read that a new addition to the canvas specification is Path() objects. Presumably these could be stored and subsequently tested and/or replayed. Could be useful. If I've understood the spec correctly.
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#path-objects
I'm currently implementing a 2d deformable terrain effect in a game I'm working on and its going alright but I can envision it becoming a performance hog very fast as I start to add more layers to the effect.
Now what I'm looking for is a way to possibly save a path, or clipping mask or similar instead of having to store each point of the path in the terrain that i need to draw through each frame. And as I add more layers I will have to iterate over the path more and more which could contain thousands of points.
Some very simple code to demonstrate what I'm currently doing
for (var i = 0; i < aMousePoints.length; i++)
{
cRenderContext.save();
cRenderContext.beginPath();
var cMousePoint = aMousePoints[i];
cRenderContext.arc(cMousePoint.x, cMousePoint.y, 30, 0, 2 * Math.PI, false);
cRenderContext.clip();
cRenderContext.drawImage(cImg, 0, 0);
cRenderContext.closePath();
cRenderContext.restore();
}
Basically I'm after an effecient way to draw my clipping mask for my image over and over each frame
Notice how your clipping region stays exactly the same except for its x/y location. This is a big plus.
The clipping region is one of the things that is saved and restored with context.save() and context.restore() so it is possible to save it that way (in other words defining it only once). When you want to place it, you will use ctx.translate() instead of arc's x,y.
But it is probably more efficient to do it a second way:
Have an in-memory canvas (never added to the DOM or shown on the page) that is solely for containing the clipping region and is the size of the clipping region
Apply the clipping region to this in-memory canvas, and then draw the image onto this canvas.
Then use drawImage with the in-memory canvas onto your game context. In other words: cRenderContext.drawImage(in-memory-canvas, x, y); where x and y are the appropriate location.
So this way the clipping region always stays in the same place and is only ever drawn once. The image is moved on the clipping-canvas and then drawn to look correct, and then the in-memory canvas is drawn to your main canvas. It should be much faster that way, as calls to drawImage are far faster than creating and drawing paths.
As a separate performance consideration, don't call save and restore unless you have to. They do take time and they are unnecessary in your loop above.
If your code is open-source, let me know and I'll take a look at it performance-wise in general if you want.
Why not have one canvas for the foreground and one canvas for the background? Like the following demo
Foreground/Background Demo (I may of went a little overboard making the demo :? I love messing with JS/canvas.
But basically the foreground canvas is transparent besides the content, so it acts like a mask over the background canvas.
It looks like it is now possible with a new path2D object.
The new Path2D API (available from Firefox 31+) lets you store paths, which simplifies your canvas drawing code and makes it run faster. The constructor provides three ways to create a Path2D object:
new Path2D(); // empty path object
new Path2D(path); // copy from another path
new Path2D(d); // path from from SVG path data
The third version, which takes SVG path data to construct, is especially handy. You can now re-use your SVG paths to draw the same shapes directly on a canvas as well:
var p = new Path2D("M10 10 h 80 v 80 h -80 Z");
Information is taken from Mozilla official site.