Fill in outer area of an interior polygon - javascript

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>

Related

Context2d arc() with begin path, generates strange circle

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>

Javascript and Canvas Draw Circle with Hole in Middle

I want to draw something like a donut, so a circle with a hole in the middle. I tried using ctx.clip(), but I realized it limits the path to inside, and I want it to limit the path to the outside.
Things to note:
this.lineWidth is how thick the "rim" or the outside portion is
ctx.beginPath();
// this should be the hole
ctx.arc(this.x,this.y,this.r,0,Math.PI*2,false);
ctx.clip();
// this should be the outside part
ctx.arc(this.x,this.y,this.r+this.lineWidth,0,Math.PI*2,false);
ctx.fillStyle = "#00ff00";
ctx.fill();
Instead I'm getting a filled-in circle because it's limiting the path to inside the smaller arc instead of outside it. Is there another method that does the opposite of clip()?
I found this solution http://jsfiddle.net/Hnw6a/:
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
//ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise);
ctx.beginPath()
ctx.arc(100,100,100,0,Math.PI*2, false); // outer (filled)
ctx.arc(100,100,55,0,Math.PI*2, true); // outer (unfills it)
ctx.fill();
With following canvas node:
<canvas id="canvas1" width="500" height="500"></canvas>
Set the linewidth to the desired width, draw your circle, and use "ctx.stroke();". Note that this doesn't allow you to fill the inner circle with a color.

JS | Slicing a polygon that has an image clipped to it, into two polygons

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>

Clipping region is not cleared by restore()

I'm trying to draw shape1 clipped to region1 and shape2 clipped to region2. My natural assumption was that the following would get me desired effect.
context.save();
context.clip(region1);
context.fill(shape1);
context.restore();
context.save();
context.clip(region2);
context.fill(shape2);
context.restore();
However, to my surprise, the first restore() doesn't cancel current clipping region. Instead, the second region is combined with the first one. That is contrary to multiple online sources which claim that restore() is the way to go when you're done with clipping.
Live example. You can see that the second fill (blue) is clipped to region1+region2, not just region2.
Even stranger, this additive behaviour is enabled by the use of save/restore calls. If I drop them, second clip call is simply ignored.
So, I have two questions.
Is there any logic to the way clipping works in html5 canvas?
How can I accomplish what I originally set out to do?
Any help in figuring this out is appreciated!
Always begin a new path drawing with context.beginPath().
Failing to do so will cause all previous drawing commands to be re-executed along with the newly added commands.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
ctx.save();
ctx.beginPath();
ctx.rect(100, 0, 100, 300);
ctx.clip();
ctx.fillStyle = "lime";
ctx.fillRect(0,0,300,300);
ctx.restore();
ctx.save();
ctx.beginPath();
ctx.rect(50, 50, 200, 200);
ctx.clip();
ctx.fillStyle = "blue";
ctx.fillRect(0,0,300,300);
ctx.restore();
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>

HTML5 Canvas - fillRect() vs rect()

In the code below, the second fillStyle overrides the color specified in first one if I use rect() and then fill() in both places (ie, both rects are green) but works as expected (ie, the first rect being blue and second being green) if I change the first rect() to fillRect(). Why is it so? I thought fillRect() was just rect() and then fill(), right?
ctx.translate(canvas.width/2, canvas.height/2);
ctx.fillStyle = "#5A9BDC";
ctx.fillRect(0, 0, rectWidth, rectHeight);
// ctx.rect(0, 0, rectWidth, rectHeight);
// ctx.fill();
ctx.translate(-canvas.width/2, -canvas.height/2);
ctx.fillStyle = "#31B131";
ctx.rect(0, 0, rectWidth, rectHeight);
ctx.fill();
Tested in Chrome | Fiddle
fillRect
.fillRect is a "stand-alone" command that draws and fills a rectangle.
So if you issue multiple .fillRect commands with multiple .fillStyle commands, each new rect will be filled with the preceeding fillstyle.
ctx.fillStyle="red";
ctx.fillRect(10,10,10,10); // filled with red
ctx.fillStyle="green";
ctx.fillRect(20,20,10,10); // filled with green
ctx.fillStyle="blue";
ctx.fillRect(30,30,10,10); // filled with blue
rect
.rect is part of the canvas's path commands.
Path commands are groups of drawings beginning with the beginPath() and continuing until another beginPath() is issued.
Within each group, only the last styling command wins.
So if you issue multiple .rect commands and multiple .fillStyle commands inside a path, only the last .fillStyle will be used on all the .rect's.
ctx.beginPath(); // path commands must begin with beginPath
ctx.fillStyle="red";
ctx.rect(10,10,10,10); // blue
ctx.fillStyle="green";
ctx.rect(20,20,10,10); // blue
ctx.fillStyle="blue"; // this is the last fillStyle, so it "wins"
ctx.rect(30,30,10,10); // blue
// only 1 fillStyle is allowed per beginPath, so the last blue style fills all
ctx.fill()
As I know there are 3 "rect" functions for canvas: fillRect, strokeRect and rect.
ctx.rect(0,0,rectWidth,rectHeight); // create some shape, there is nothing on the canvas yet
ctx.stroke(); // draw stroke of the shape
ctx.fill(); // fill the shape
There are two shortcuts:
ctx.strokeRect(0,0,rectWidth,rectHeight); // shortcut to stroke rectangle
ctx.fillRect(0, 0, rectWidth, rectHeight); // shortcut to fill rectangle
So, your fill invocation could fill only your shape created with rect.
If you want different colors for different path commands, calling beginPath() before each command works.
ctx.beginPath();
ctx.fillStyle="red";
ctx.rect(10,10,10,10);
ctx.fill()
ctx.beginPath()
ctx.fillStyle="green";
ctx.rect(20,20,10,10);
ctx.fill()
ctx.beginPath()
ctx.fillStyle="blue";
ctx.rect(30,30,10,10);
ctx.fill()

Categories