I'm trying to write a game engine in pure javscript and canvas.
I got a strange thing today, while generating a circle with
var circle = new Circle(10, 10, 100);
The circle is weird, if I remove from the code beginPath() and closePath() and it works as it should, but then the screen is not redraw due to: http://codetheory.in/why-clearrect-might-not-be-clearing-canvas-pixels/
The "engine" code can be found here: insane96mcp.altervista.org/Invaders/script.js
I can't reproduce your weird circle from the code in your link, but...
1. You appear to have misconceptions about beginPath & endPath
beginPath starts a new path and stops drawing the previous path. It is necessary to prevent your drawings from accumulating and overwriting themselves. Without beginPath your circle will redraw (& redraw & redraw & redraw!) itself with every call to yourCircle.Draw. This constant redrawing might be causing your weird circle if you leave out beginPath.
closePath is not the counter-part to beginPath. It does not stop drawing a path. Instead it simply connects the current path position to the beginning path position with a line. Without beginPath, these extra lines created by closePath might be the cause of your jagged circle.
Some reading for you below (context.beginPath and context.closePath)
2. You're un-necessarily adding "empty" Shape objects into gameObjects in your function Shape.
3. ...And yes, as you surmise in your question, if you're not clearing the canvas between draws with clearRect then your drawings will accumulate. But one would expect the circle to be more uniformly fuzzy instead of jaggedy.
context.beginPath
context.beginPath()
Begins assembling a new set of path commands and also discards any previously assembled path.
It also moves the drawing "pen" to the top-left origin of the canvas (==coordinate[0,0]).
Although optional, you should ALWAYS start a path with beginPath
The discarding is an important and often overlooked point. If you don't begin a new path with beginPath, any previously issued path commands will automatically be redrawn.
These 2 demos both attempt to draw an "X" with one red stroke and one blue stroke.
This first demo correctly uses beginPath to start it's second red stroke. The result is that the "X" correctly has both a red and a blue stroke.
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
// get a reference to the canvas element and it's context
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// draw a blue line
ctx.beginPath();
ctx.moveTo(30,30);
ctx.lineTo(100,100);
ctx.strokeStyle='blue';
ctx.lineWidth=3;
ctx.stroke();
// draw a red line
ctx.beginPath(); // Important to begin a new path!
ctx.moveTo(100,30);
ctx.lineTo(30,100);
ctx.strokeStyle='red';
ctx.lineWidth=3;
ctx.stroke();
}); // end window.onload
</script>
</head>
<body>
<canvas id="canvas" width=200 height=150></canvas>
</body>
</html>
This second demo incorrectly leaves out beginPath on the second stroke. The result is that the "X" incorrectly has both red strokes.
The second stroke() is draws the second red stroke.
But without a second beginPath, that same second stroke() also incorrectly redraws the first stroke.
Since the second stroke() is now styled as red, the first blue stroke is overwritten by an incorrectly colored red stroke.
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
// get a reference to the canvas element and it's context
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// draw a blue line
ctx.beginPath();
ctx.moveTo(30,30);
ctx.lineTo(100,100);
ctx.strokeStyle='blue';
ctx.lineWidth=3;
ctx.stroke();
// draw a red line
// Note: The necessary 'beginPath' is missing!
ctx.moveTo(100,30);
ctx.lineTo(30,100);
ctx.strokeStyle='red';
ctx.lineWidth=3;
ctx.stroke();
}); // end window.onload
</script>
</head>
<body>
<canvas id="canvas" width=200 height=150></canvas>
</body>
</html>
context.closePath
context.closePath()
Draws a line from the current pen location back to the beginning path coordinate.
For example, if you draw 2 lines forming 2 legs of a triangle, closePath will "close" the triangle by drawing the third leg of the triangle from the 2nd leg's endpoint back to the first leg's starting point.
A Misconception explained!
This command's name often causes it to be misunderstood.
context.closePath is NOT an ending delimiter to context.beginPath.
Again, the closePath command draws a line -- it does not "close" a beginPath.
This example draws 2 legs of a triangle and uses closePath to complete (close?!) the triangle by drawing the third leg. What closePath is actually doing is drawing a line from the second leg's endpoint back to the first leg's starting point.
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
// get a reference to the canvas element and it's context
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// arguments
var topVertexX=50;
var topVertexY=50;
var rightVertexX=75;
var rightVertexY=75;
var leftVertexX=25;
var leftVertexY=75;
// A set of line segments drawn to form a triangle using
// "moveTo" and multiple "lineTo" commands
ctx.beginPath();
ctx.moveTo(topVertexX,topVertexY);
ctx.lineTo(rightVertexX,rightVertexY);
ctx.lineTo(leftVertexX,leftVertexY);
// closePath draws the 3rd leg of the triangle
ctx.closePath()
ctx.stroke();
}); // end window.onload
</script>
</head>
<body>
<canvas id="canvas" width=200 height=150></canvas>
</body>
</html>
Related
I'm drawing a polygon to the canvas, which has an image clipped inside of it.
On touch start and end, I'm slicing that polygon into two polygons and rotating them and pushing them away from each other. Similar functionality to Fruit Ninja.
My issue is drawing the image to the new polygons, so make it look like you have sliced the image in half and it's now rotating away.
Does anyone have any advice. I don't necessarily need code, I just need to understand how it's done so I can program it.
Thanks heaps.
Infinite designs are possible, but for example...
Use translate to set the rotation point outside the polygon.
rotate to a positive angle and draw the left half of the poly.
Reset the transformations.
Use translate to set the rotation point outside the polygon.
rotate to a negative angle and draw the right half of the poly.
Reset the transformations.
Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var cx=50;
var cy=20;
var r=20;
var angle=0;
ctx.fillStyle='salmon';
ctx.strokeStyle='forestgreen';
ctx.lineWidth=3;
requestAnimationFrame(animate);
function draw(angle){
ctx.clearRect(0,0,cw,ch);
// left side
ctx.translate(cx,cy+40);
ctx.rotate(angle);
ctx.beginPath();
ctx.arc(cx,cy,r,Math.PI/2,Math.PI*3/2);
ctx.fill();
ctx.stroke();
ctx.setTransform(1,0,0,1,0,0);
// right side
ctx.translate(cx,cy+40);
ctx.rotate(-angle);
ctx.beginPath();
ctx.arc(cx,cy,r,Math.PI*3/2,Math.PI/2);
ctx.fill();
ctx.stroke();
ctx.setTransform(1,0,0,1,0,0);
}
function animate(){
draw(angle);
angle+=Math.PI/720;
if(angle<Math.PI/4){ requestAnimationFrame(animate); }
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=200 height=200></canvas>
I'm drawing a ship with 4 points in a shipVertexes array.
Inside the ship, I'm drawing the same shape smaller with the stunVertexes.
Stroking the context yields the attached image. You can see how there's a small ship inside the larger one. (Red ship)
I want to fill in the space between the outer ship & the inner ship, leaving an empty (black) space in the middle.
When I run context.fill(), the entire space is filled solid (see Yellow ship).
How do I fill in just the outside of the interior space?
There is a faster way to do that other than using 2 filling operation and global compositing.
Also note that using compositing for erasing will erase wathever is in the hole of the ship. if there is some decoration in the background that will be lost. In this situation it looks correct because you have an empty background that is showing the body tag color.
Fill operation has two way to understand what is inside the shape and what is outside. The most commonly used is the non-zero rule:
‘non-zero’ winding
This winding rule is most commonly used and was also the only rule that was supported by Canvas 2D.
To determine if a point falls inside the curve, you draw an imaginary line through that point. Next you will count how many times that line crosses the curve before it reaches that point. For every clockwise rotation, you subtract 1 and for every counter-clockwise rotation you add 1.
To apply the non-zero windind to your ship i changed the drawing order of the internal ship to be counterclockwise, and it automatically produces the hole.
If changing the order is too much complicated you can check if your browser support the even odd rule:
‘even-odd’ winding
To determine if a point falls inside the path, you once again draw a line through that point. This time, you will simply add the number of times you cross a path. If the total is even, the point is outside; if it’s odd, the point is inside. The winding of the path is ignored. For example:
for this rule i just copied your original code, and when filling i specified 'evenodd';
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
ctx.fillStyle='red';
//ship copy 1
ctx.beginPath();
ctx.moveTo(50,0);
ctx.lineTo(100,25);
ctx.lineTo(50,150);
ctx.lineTo(0,25);
ctx.moveTo(50,10);
ctx.lineTo(20,25);
ctx.lineTo(50,100);
ctx.lineTo(80,25);
ctx.closePath();
ctx.fill();
//ship copy 2
ctx.beginPath();
ctx.translate(180, 0);
ctx.moveTo(50,0);
ctx.lineTo(100,25);
ctx.lineTo(50,150);
ctx.lineTo(0,25);
ctx.moveTo(50,10);
ctx.lineTo(80,25);
ctx.lineTo(50,100);
ctx.lineTo(20,25);
ctx.closePath();
ctx.fill('evenodd');
body{ background-color: black; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>
Most simple is to fill all the ship in red, then fill the inner ship in black :
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(100, 25);
ctx.lineTo(50, 150);
ctx.lineTo(0, 25);
ctx.fillStyle = 'red';
ctx.fill();
ctx.beginPath();
ctx.moveTo(50, 10);
ctx.lineTo(20, 25);
ctx.lineTo(50, 100);
ctx.lineTo(80, 25);
ctx.closePath();
ctx.fillStyle = 'black';
ctx.fill();
body {
background-color: black;
}
#canvas {
border: 1px solid red;
}
<canvas id="canvas" width=300 height=300></canvas>
Use compositing to "erase" the smaller shape from the bigger shape.
In particular, destination-out compositing will "erase" using new drawings.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
ctx.fillStyle='red';
ctx.beginPath();
ctx.moveTo(50,0);
ctx.lineTo(100,25);
ctx.lineTo(50,150);
ctx.lineTo(0,25);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(50,10);
ctx.lineTo(80,25);
ctx.lineTo(50,100);
ctx.lineTo(20,25);
ctx.closePath();
// use compositing to "erase" the smaller shape from the bigger shape
ctx.globalCompositeOperation='destination-out';
ctx.fill();
ctx.globalCompositeOperation='source-over';
body{ background-color: black; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>
I want to draw a couple of simple lines to a canvas but it will not work if I do not have beginPath() and restore() in my scripting code. Do you have to have these to draw something to the HTML 5 Canvas?
Code:
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<script>
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.lineWidth="5";
ctx.strokeStyle="green"; // Green path
ctx.moveTo(0,75);
ctx.lineTo(250,75);
ctx.strokeStyle="purple"; // Purple path
ctx.moveTo(50,0);
ctx.lineTo(150,130);
</script>
</body>
</html>
A canvas deals with paths.
So, as the name suggests, beginPath will starts a new path.
Each retained draw command you will call after-wise will add-up to the current path.
( retained draw command are commands that have no direct effect : moveTo, lineTo, (Curve)To, arc, rect. )
Rq1 : Using moveTo is a special command : it will begin a new sub-path, it's like releasing the pen from the paper.
Rq2 : closePath is not required, use it if you want to link the last point to the first one easily.
When using fill or stoke, the current retained path (=all the sub-path of the current path) will get stroked/filled using current transform, clipping and color setting.
Notice that there are also direct command : fill/strokeRect, fill/strokeText, drawImage, get/putImageData.
Those commands do not require to begin a new path, and do not affect the current path -they directly draw on the canvas-.
So rule is simple :
• if you use retained commands, you must use beginPath.
• If you use direct commands do not use beginPath, just send the command.
As you noticed, you can change a lot of settings on the canvas :
• you have render settings : strokeStyle / fillStyle / globalCompositeOperation / globalAlpha / lineWidth / font / textAlign/ shadow / ...
• you have transform settings that allow you to translate/scale/rotate the next draw.
• you can define clipping to avoid the draw to occur in some areas.
save() and restore() allows you not to get lost in the status of the current canvas.
Rule is : anyway you change the context in a way that could affect the render of other drawings you make, just save() before, and restore after.
example 1 :
function strokeLine ( x1, y1, x2, y2, color) {
ctx.beginPath();
ctx.strokeStyle = color;
ctx.moveTo(x1, y1);
ctx.lineTo(x2,y2);
ctx.stroke();
}
i used moveTo/lineTo (retained) command, so without thinking i use beginPath.
here the strokeStyle is set on each call, so no need to save context.
example 2 :
function drawImageRotated( img, x, y, rotation ) {
ctx.save();
ctx.translate(x + img.width/2, y + img.height/2); // translate to the middle of the image
ctx.rotate(rotation); // rotate context
ctx.drawImage(img, x - img.width/2, y - img.height/2 ); // draw the image in rotated context
ctx.restore();
}
here no need to use beginPath() since i use only a direct command (drawImage).
But concerning canvas status, i don't want the context to remain translated/rotated after the call. So i started by a save, and ended by a restore, so the caller keeps the same context after the call.
beginPath() is used to start drawing a contour, you will need it. Then we use stroke() to actually draw to the HTML5 Canvas visually.
ctx.beginPath(); // Start drawing
// Drawing Methods Here
ctx.stroke() // Show what we drew.
Fiddle
restore() is for changes in the state of the Canvas: rotation, scaling, skew, etc. This method reverts it to the last state of the Canvas that was saved. But it isn't used for actually drawing on our canvas, but changing it's state.
I'm dynamically drawing a polygon inside a canvas using this code I found. (the coordinates are provided by the user)
https://stackoverflow.com/a/4841057/1667856
Is it possible to move this polygon into the center of a canvas after/before it has been created?
I found out that you can use the canvas translate() method to move shapes around but I can't seem to figure out how to recalculate the coordinates so that the polygon will automatically appear in the middle of the canvas and not on its original coordinates.
As you've probably discovered, html canvas is just a pixel drawing.
The shapes you draw on the canvas are just like dried paint. They can't be moved or remolded into another shape.
The typical way of "moving" a shape is to clear the canvas and redraw the same shape with different coordinates.
You can create those coordinates by adding an offsetX and offsetY to all the polygon coordinates.
Alternatively (more simply) you can translate the coordinates.
Important note: context.translate does not just move the coordinates of your polygon. It changes every coordinate for all NEW drawings.
ctx.translate(50,50) "moves" the canvas's origin to 50,50. That means if you start drawing your polygon at 5,5 you will visually start drawing at 50+5,50+5.
Here is example code and a Demo: http://jsfiddle.net/m1erickson/2Gm73/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// calculate the middle of the canvas
var centerX=canvas.width/2;
var centerY=canvas.height/2;
// just for testing: draw crosshairs on center canvas
ctx.beginPath();
ctx.moveTo(centerX,0);
ctx.lineTo(centerX,canvas.height);
ctx.moveTo(0,centerY);
ctx.lineTo(canvas.width,centerY);
ctx.stroke();
// define some points for your polygon
var poly=[ 5,5, 100,50, 50,100, 10,90 ];
// save the canvas context in its untranslated state
ctx.save();
// translate the canvas
// the context now uses centerX,centerY as its 0,0 origin
ctx.translate(centerX,centerY);
// draw the polygon
ctx.beginPath();
ctx.moveTo(poly[0], poly[1]);
for(var i=2;i<poly.length;i+=2){
ctx.lineTo( poly[i] , poly[i+1] )
}
ctx.closePath();
ctx.fillStyle = '#f00';
ctx.fill();
// restore the context to its untranslated state
// (otherwise all further drawings will be "moved"
// just like this polygon
ctx.restore();
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
If you want your polygon to be visually centered in the canvas, you must also calculate the offset of the center of your polygon itself:
// calc the max values of x,y in the polygon and divide by 2
var centerPolyX = 100/2; // max x comes from coordinate 100,50
var centerPolyY = 100/2; // max y comes from coordinate 50,100
Then translate to center canvas minus the polygon center:
// move to center canvas
// but then move left and up by half the polygon's size
ctx.translate(centerX-centerPolyX,centerY-centerPolyY);
I'm working on some code which is drawing to a canvas. One part of the code draws some lines onto the canvas. The position and colour of those lines don't change, but they often need to be redrawn because other code may have affected it (eg: drawn over the top of it).
There can be several hundred lines to draw, and in these cases, profiling shows me that it's taking ~200ms to draw, so I'm looking to optimise this somewhat.
One thing I noticed was that when drawing to the canvas, you basically are adding points to a path and then once ready, you can fill or stroke that path. Though the pixels on the canvas are out of date, if I were able to keep a reference to the path, then updating would be as simple as re-stroking the previously constructed path.
My question is: how on earth do you get a Path object?
The fill and stroke methods appear to accept a path object, and the spec defines the methods for Path, but I can't seem to find the actual Path class anywhere...
So, just to recap:
I have something like this:
function update() {
context.beginPath();
// lots of lines added to the default path...
context.moveTo(x1, y1); context.lineTo(somewhere, else);
context.moveTo(x2, y2); context.lineTo(somewhere, else);
context.stroke();
}
What I'd like is something like this:
function update() {
if (!this.path) {
this.path = new Path(); // <-- here's the magic
this.path.moveTo(x1, y2); this.path.lineTo(somewhere, else); // etc
}
this.path.stroke();
}
The canvas spec calls for a Path object that is not implemented in browsers yet.
BTW, when implemented, the Path object will be useful in hit-testing when combined with context.isPointInPath(myPath); Someday...
Here's how you could create your own Path object until the browsers catch up:
Create a JS object that contains a canvas where your path strokes are drawn.
When you want to do myPath.stroke(), use myVisibleContext.drawImage(myPath.context,0,0) to "blit" the path's canvas onto your drawing canvas.
Demo: http://jsfiddle.net/m1erickson/QLJv8/
Code:
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
function Path(maxWidth,maxHeight,color,linewidth,drawingContext){
this.width=maxWidth;
this.height=maxHeight;
this.drawingCtx=drawingContext;
this.points=[]
this.canvas=document.createElement("canvas");
this.canvas.width=maxWidth;
this.canvas.height=maxHeight;
this.ctx=this.canvas.getContext("2d");
this.ctx.strokeStyle=color;
this.ctx.lineWidth=linewidth;
this.lastX;
this.lastY;
}
Path.prototype.moveTo=function(x,y){
this.lastX=x;
this.lastY=y;
}
Path.prototype.lineTo=function(x,y){
this.ctx.moveTo(this.lastX,this.lastY);
this.ctx.lineTo(x,y);
this.ctx.stroke();
this.lastX=x;
this.lastY=y;
}
Path.prototype.stroke=function(){
this.drawingCtx.drawImage(this.canvas,0,0);
}
// create a new path object
var p=new Path(300,300,"blue",2,ctx);
// set the Path's drawing commands
p.moveTo(69,91);
p.lineTo(250,150);
p.moveTo(69,208);
p.lineTo(180,54);
p.lineTo(180,245);
p.lineTo(69,91);
p.moveTo(69,208);
p.lineTo(250,150);
// draw the Path.canvas to the drawing canvas
p.stroke();
// tests...
$("#stroke").click(function(){
p.stroke();
});
$("#erase").click(function(){
ctx.clearRect(0,0,canvas.width,canvas.height);
});
}); // end $(function(){});
</script>
</head>
<body>
<button id="stroke">Path.stroke</button><br>
<button id="erase">Erase main canvas</button><br>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
Turns out, it's just that no browser supports it yet, according to this blog (dated 24th January 2013) http://www.rgraph.net/blog/2013/january/html5-canvas-path-objects.html
Their is no path support in canvas, but why don't use svg line and set its zIndex to be on top of others.
None of the canvas drawing API let you hold references to objects.
Canvas lets you draw pixels in a bitmap, not create and manipulate objects like SVG does.
If you're looking to optimize performance and you want to reuse the same path over and over again, you might want to draw it once in a separate canvas oject, and then draw that canvas into your other canvas using drawImage (which can take a canvas as argument).