HTML5 Canvas - fillRect() vs rect() - javascript

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()

Related

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.

Fill in outer area of an interior polygon

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>

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>

Drawing a circle inside another circle canvas

I am trying to draw a gear in Canvas but running into issues from the start. I want to have a filled circle with a hallowed out middle. Instead, I am getting what looks to be an outline of a single circle.
Here is my code:
var ctx = document.getElementById("canvas").getContext("2d"),
i = 0;
function drawGear(){
ctx.fillStyle = "#000";
ctx.translate(50,50);
ctx.arc(0,0,20,0,Math.PI*2);
ctx.fill();
ctx.fillStyle = "#FFF";;
ctx.arc(0,0,5,0,Math.PI*2);
ctx.fill();
}
drawGear();
http://jsfiddle.net/te3jn/
I believe that the issue is something related to the globalCompositeOperation, but I tried several of them (source-over, source-atop, destination-over) and none seem to work the way I want.
You should begin a new path when drawing the second circle, like this:
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "#FFF";
// ...
JS Fiddle.
Without this, you'll essentially redraw both circles - the inner and the outer one - with the second fill call (check this fiddle for demonstration)

Adding border to canvas elements

I am drawing some arrows in canvas (similar arrows can be viewed in a life cycle). I want to draw border to all these arrows individually just like we add to divs. I tried using stroke style and stroke method but it filled my entire arrow.
I am using fill style and fill to fill color to my arrows.
Is there any way to do it? Is it like fill and stroke methods can never be used together?
You'll have to use beginPath() and closePath() on your canvas's context ("ctx" down here), followed by a fill() to actually fill the element:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.moveTo(170, 80);
ctx.lineTo(300,150);
ctx.lineTo(100,150);
ctx.lineTo(170, 80);
// Etc, Make your movements to draw the arrow, here.
ctx.closePath();
//Line settings and drawing
ctx.lineWidth = 5;
ctx.strokeStyle = 'blue';
ctx.stroke();
//Fill settings and drawing
ctx.fillStyle = '#8ED6FF';
ctx.fill();

Categories