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>
Related
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.
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>
So I'm creating a simple userscript for me - I want to draw a play button over shaded version of any GIF image on the site to have them only played when I want to.
I have inspired myself here. To make the whole thing nice, I'm drawing a green circle over the animation. This is my intended effect (I really wonder if once can make some shapes in GIMP):
And this is what I currently have:
The Cleese ironic face animation comes from this GIF portal
I made a fiddle of my current GIF pauser userscript.
The critical code for printing the circle:
//Assume these variables
var ctx = canvas context (2d)
var w = canvas width
var h = canvas height
//The code:
ctx.beginPath();
//I'm making sure the circle will always fit - therefore Math.min
ctx.arc(w/2, h/2, Math.min(60, w/2-20, h/2-20), 0, 2 * Math.PI, false);
ctx.fillStyle = 'rgba(0,180,0,0.5)';
ctx.fill();
ctx.lineWidth = 1;
ctx.strokeStyle = 'red';
ctx.stroke();
ctx.closePath();
I hope it's clear that the triangle must always fit into the circle (size of which may vary). It must have the vertical line a little bit shorter then the other two, which must be of equal length.
I'm totally not interested in any existing GIF pausing libraries. Keep in mind that the question is about the triangle, not the GIFs.
Like this? http://jsfiddle.net/32ECU/
//Draw a triangle in it
ctx.strokeStyle = 'white';
ctx.beginPath();
ctx.moveTo(w/3, h/3);
ctx.lineTo(w/3, h-h/3);
ctx.lineTo(w-w/4, h/2);
ctx.lineTo(w/3, h/3);
ctx.fillStyle = 'white';
ctx.fill();
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)