Jigsaw Puzzle pices using Bezier Curve - html

I was trying to make some jigsaw pieces like this -
What I have tried till now with lineTo -
outside: function (ctx, s, cx, cy) {
ctx.lineTo(cx, cy)
ctx.lineTo(cx+s*.3, cy)
ctx.lineTo(cx+s*.5, cy+s*-.2)
ctx.lineTo(cx+s*.7, cy)
ctx.lineTo(cx+s, cy)
},
inside: function (ctx, s, cx, cy) {
ctx.lineTo(cx, cy)
ctx.lineTo(cx+s*.3, cy)
ctx.lineTo(cx+s*.5, cy+s*+.2)
ctx.lineTo(cx+s*.7, cy)
ctx.lineTo(cx+s, cy)
},
Fiddle Link

Efficient Jigsaw design is simple and it works like this:
The linked code already shows how to efficiently assemble one of your jigsaw pieces by reusing a single side design.
The piece on the right side of you illustration is a traditional (or "Japanese Style") piece. This means its sides are uniform in length and fully interlocking. Japanese style pieces are the easiest to design because a single piece of design code and be reused throughout the puzzle.
Ironically, While Japanese Style puzzles are the easiest to code, they are more difficult for the user to solve since many pieces will physically fit together without correctly solving the puzzle.
How to design a Japanese Style jigsaw puzzle
Design one side (not more!) of a jigsaw piece by combining multiple cubic Bezier curves.
Use transforms to apply that one jigsaw design to the top, right, bottom or left sides as needed. (or code functions that automatically manipulate the original Bezier control points to apply that one jigsaw design to the 4 sides). Mirror the original side design to give your pieces a variety of "inny" and "outy" sides.
Assemble a puzzle from pieces by mirroring the design of each neighboring side:
Give the top-left piece (0,0) a random right side (either inny or outy).
Let's assume piece (0,0) was assigned an outy right side. Then the next piece to the right (1,0) must get an inny left side.
Now give piece (1,0) a random right side (either inny or outy), and piece (2,0) must get the mirrored type of side. And so on...
So in general, fill the puzzle by assigning random right sides to all the pieces and mirroring the assigned side on the left side of the next piece.
Do the same vertically. fill the puzzle by assigning random bottom sides to all the pieces and mirroring the assigned side on the top side of the next piece.
Designing the 2 example pieces you've illustrated
I assume the linked code is not your code because it already shows how to design that piece on the right of your illustration(!).
// Given the center point of the piece (cx,cy) and the side length (s)
// The single side "outy" design is below
// Use this single design (with transforms/mirroring) to make all pieces
ctx.lineTo(cx + s * .34, cy);
ctx.bezierCurveTo(cx + s * .5, cy, cx + s * .4, cy + s * -.15, cx + s * .4, cy + s * -.15);
ctx.bezierCurveTo(cx + s * .3, cy + s * -.3, cx + s * .5, cy + s * -.3, cx + s * .5, cy + s * -.3);
ctx.bezierCurveTo(cx + s * .7, cy + s * -.3, cx + s * .6, cy + s * -.15, cx + s * .6, cy + s * -.15);
ctx.bezierCurveTo(cx + s * .5, cy, cx + s * .65, cy, cx + s * .65, cy);
ctx.lineTo(cx + s, cy);
Then you can reuse this one single set of Bezier curves along with transformations to create your entire puzzle. Transformations==moving, rotating and mirroring the one single design to make up any side of any puzzle piece.
The piece on the left of your illustration is probably from a Freeform Style jigsaw puzzle. It is more complex because it uses 3 different side designs. I assume there are additional side designs which you haven't shown because the 3-sided design you show would not allow all pieces to interlock to complete the puzzle.
You have several options when creating a Freeform Style jigsaw puzzle.
Non-interlocking Freeform Style
In this style, you basically take an image and draw lines that cut it into non-uniform pieces that can be arranged to form the image. Think of this like a pizza that's been sliced randomly. You can fit the pieces together to reform the pizza even if the pieces do not interlock. Mmmmm, pizza! :-)
Interlocking Freeform Style
In this style, you design 2+ sides and create the puzzle much the same way as the traditional style puzzle. Usually you create one design that you will use for all left-right sides and a second design that you will use for all top-bottom sides. The complexity is that the 2 types of sides must fit together where they meet. This means that side-type-1 must share a mirrored pattern where it intersects side-type-2.
So to design the piece on the left side of your illustration, you must decide if you want it to be Interlocking-Freeform or Non-interlocking-Freeform.
Non-interlocking Freeform is the easier. Just pull apart the 3 types of sides and use them with their mirrored partners to chop up your image.
For Interlocking-Freeform, more design work is necessary on your part. You must create additional side designs that will interlock with the 3 designs you've already created.
That's a quick tour of jigsaw puzzles...Good luck with your project!
[ Additional details ]
For the piece on the right side of your illustration, the common "outside" looks like a "shoulders & head" silhouette.
The Bezier-set to create the shoulders & head break down like this:
A Bezier for the "left shoulder"
A Bezier for the "left neck"
A Bezier for the "left head"
A Bezier for the "right head"
A Bezier for the "right neck"
A Bezier for the "right shoulder"
A shoulder & head Bezier set might look like this:
Here's one specific example of the control points to create an outside side with a "shoulders & head" shape:
var ShouldersAndHeadCubicBezierControlPoints=[
{cx1:0, cy1:0, cx2:35,cy2:15, ex:37, ey:5}, // left shoulder
{cx1:37, cy1:5, cx2:40,cy2:0, ex:38, ey:-5}, // left neck
{cx1:38, cy1:-5, cx2:20,cy2:-20,ex:50, ey:-20}, // left head
{cx1:50, cy1:-20,cx2:80,cy2:-20,ex:62, ey:-5}, // right head
{cx1:62, cy1:-5, cx2:60,cy2:0, ex:63, ey:5}, // right neck
{cx1:63, cy1:5, cx2:65,cy2:15, ex:100,ey:0}, // right shoulder
];
Once you have the "outside" set of curves, you can use canvas's context transformations to flip the "outside" into its mirrored "inside". Alternatively you can manually reverse the "outside" array of curve control points.
Illustrations: top-tab and top-slot (top-slot is top-tab mirrored)
Example displaying top-tab and top-slot:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
ctx.lineWidth=3;
var colors=['red','green','blue','gold','purple','cyan'];
var bSet=makeBeziers();
draw(bSet,50,100);
var bSetMirrored=mirror(bSet,1,-1,0,0);
draw(bSetMirrored,50,200);
function draw(bSet,transX,transY){
ctx.translate(transX,transY);
ctx.scale(2,2);
for(var i=0;i<bSet.length;i++){
var b=bSet[i];
ctx.beginPath();
ctx.bezierCurveTo(b.cx1,b.cy1,b.cx2,b.cy2,b.ex,b.ey);
ctx.strokeStyle=colors[i];
ctx.stroke();
}
ctx.setTransform(1,0,0,1,0,0);
}
function makeBeziers(){
return([
{cx1:0, cy1:0, cx2:35,cy2:15, ex:37, ey:5}, // left shoulder
{cx1:37, cy1:5, cx2:40,cy2:0, ex:38, ey:-5}, // left neck
{cx1:38, cy1:-5, cx2:20,cy2:-20,ex:50, ey:-20}, // left head
{cx1:50, cy1:-20,cx2:80,cy2:-20,ex:62, ey:-5}, // right head
{cx1:62, cy1:-5, cx2:60,cy2:0, ex:63, ey:5}, // right neck
{cx1:63, cy1:5, cx2:65,cy2:15, ex:100,ey:0}, // right shoulder
]);
}
function mirror(b,signX,signY,x,y){
var a=[];
for(var i=0;i<b.length;i++){
var bb=b[i];
a.push({
cx1: bb.cx1*signX+x,
cy1: bb.cy1*signY+y,
cx2: bb.cx2*signX+x,
cy2: bb.cy2*signY+y,
ex: bb.ex*signX+x,
ey: bb.ey*signY+y
});
}
return(a);
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>

I would like to propose an alternative approach -
SVG source
Why not consider using a SVG image source that you make in Illustrator or some similar software. The tools will allow you to trace the outline of a piece (it can in fact do it almost entirely automatic - see result below). The SVG may use Beziers too, or just a path that fits the original image almost perfectly. Just hit trace, adjust threshold, save out and clean up the result a little.
This way you can simply import the SVG, draw it to an off-screen canvas at the size you want to rasterize it. The result can then be used for either composition or "cutting out" graphics directly.
This is much faster and simpler than calculating and using splines.
Example
This result is from Illustrator converted to SVG, with some manual clean-up such as removing comments and background, adding width and height and adjusting viewbox:
(Note: in this demo I left the shadow in the original image - you would have to clean up the image itself, then process it to SVG etc.).
I can now load it as a basis for composition so I could do:
var ctx = document.querySelector("canvas").getContext("2d"),
svg = document.querySelector("svg").outerHTML,
img = new Image();
// fill some graphics to canvas
ctx.fillStyle = "#777";
ctx.fillRect(0, 0, 600, 600);
// load SVG so we can use the puzzle with canvas
img.onload = demo;
img.src = "data:image/svg+xml;base64," + btoa(svg);
function demo() {
// create a matte so it becomes rasterized - choose the size dynamically if you need to
var matte = document.createElement("canvas"),
mctx = matte.getContext("2d");
matte.width = matte.height = 100;
mctx.drawImage(this, 0, 0, 100, 100); // draw in (rasterize) the SVG
// we can now use the puzzle as basis for an image region, or to mask out parts:
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(matte, 10, 10);
ctx.drawImage(matte, 100, 100);
ctx.drawImage(matte, 210, 10);
}
canvas {border:1px solid #000;background:url(http://i.stack.imgur.com/bEiyx.jpg)}
<canvas width=600 height=600></canvas>
<br><br>SVG (inlined for demo - use from URL instead):<br>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg version="1.1" baseProfile="tiny" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" viewBox="0 0 90 90" width="300" height="300" xml:space="preserve">
<path d="M16.1,34.2c-2.8,2.3-4.4,4.3-6.5,5.1c-1.9,0.7-5.2,1.2-6.3,0.1C1.9,38,1,34.6,1.7,32.7c0.9-2.6,3-5.2,5.3-6.7
c6.5-4.4,6.5-4.3,3.9-11.8c-0.5-1.4,0.6-4.7,1.6-5c2.6-0.7,5.8-1,8.2,0c1.2,0.4,2.4,4.4,1.7,5.8c-2.3,4.9-0.3,7.7,3.5,10
c3.4,2,6.7,1.5,9.3-1.8c2.4-3.1,1.9-5.9-1.1-8.3c-3.2-2.6-2.7-5.1,0.1-7.4c3.6-3.1,12.4-3.2,16-0.2c2.7,2.2,3.9,4.6,0.6,7.4
c-2.7,2.3-4.1,4.9-1.6,8.2c2.4,3.2,5.6,4.2,9.2,2.3c3.5-1.8,5-4.7,4-8.7c-1.5-5.7,1-9.2,6.2-8.9c4.4,0.2,6.8,4.4,4.3,8
c-2.5,3.7-1.6,6.1,1.8,8.3c1.7,1.1,3.3,2.3,4.9,3.5c3.9,3,5,9.1,2.2,12c-2.2,2.3-6.4,1.4-10.2-2.2c-0.7-0.7-1.3-1.4-2-2.1
c-5.7,5.4-6.2,12.7-1.6,17.8c1.9,2.1,4.2,3.1,6.4,0.8c2.1-2.1,3.8-4.5,6.9-1.4c2.7,2.8,3.5,8,0.8,10.4c-2,1.7-4.8,2.9-7.4,3.4
c-4.9,0.9-6.2,3.5-3.8,8c0.9,1.7,0.5,4.1,0.8,6.1c-2.2-0.3-5.2,0.2-6.5-1.1c-2.4-2.4-3.8-5.7-5.4-8.7c-2.2-4.2-4.2-4.6-9.5-2.1
c-4,1.9-3.1,4.4-1.3,7.3c1.9,3.1,4.5,6.5-0.3,9.4c-4,2.5-13.5,1.7-14.8-1.6c-0.8-2.1,0-5.2,1.2-7.3c2-3.5,2.8-6-1.7-7.9
c-5.4-2.4-7.1-1.7-9.5,3.2c-1.6,3.2-3.6,6.3-6.1,8.8c-0.9,0.9-3.7-0.1-5.7-0.3c0.2-1.8-0.2-3.9,0.6-5.3c3.4-5.3,2.4-7.6-4-8.3
c-7-0.8-10.5-6.7-7.1-12.5c1.4-2.4,3.2-4,6-1.9c2.2,1.7,4.7,4.9,7.2,2.1c2.4-2.6,4.1-6.5,4.3-10C19.7,40.9,17.5,37.8,16.1,34.2z"/>
</svg>
And of course, if you don't want to use it as matte you can draw a piece of the image on top of it every time you need to update a piece on the board (use source-atop for example to replace the image on the same "matte").

Related

SkiaSharp - how to create a hatch or dotted pattern on the filled space between curves

In SkiaSharp I can nicely fill the space between two curves by using SKPathFillType.EvenOdd. Below I show a simplified excerpt from the code.
My question is how can I give a certain pattern to this filled area between the curves ? Here I can only fill it with a color and give it a transparency. I'm interested in applying a pattern, such as hatch or dots.
Thank you for any support.
Greetings,
Sorin
SKPath path = new SKPath();
path.FillType = SKPathFillType.EvenOdd;
// start the first curve
path.MoveTo(....);
path.LineTo(....); // draw the curve and close it
....
path.AddCircle(....); // add a second curve as a circle
SKPaint paint = new SKPaint(new SKFont(SKTypeface.Default)) {
IsAntialias = true,
Style = SKPaintStyle.Fill,
Color = SKColors.Blue.WithAlpha((byte)(0xFF * (1 - 0.5))),
StrokeWidth = 1
};
canvas.DrawPath(path, paint);
I've managed to fix this with a trick.
First of all, I do all I wrote above, i.e.
canvas.DrawPath(path, paint)
.... will draw a filled area between the two curves, with a certain transparency.
On top of that (literally), I draw another pattern:
var hatch = new SKPath();
hatch.AddCircle(0, 0, 1);
var hatchPaint = new SKPaint {
PathEffect = SKPathEffect.Create2DPath(SKMatrix.MakeScale(7, 7), hatch),
Color = SKColors.RosyBrown,
Style = SKPaintStyle.Stroke,
StrokeWidth = 3
};
And again:
canvas.DrawPath(path, hatchPaint);
This draws a nice hatch pattern on top of the filled area between the curves.
Note: the size of the pattern is essential - here AddCircle(0, 0, 1), where the circle ray is 1 pixel. If you have a larger one, the hatch pattern will spill out the filled area, which is not what you want. To me this looks like a bug in SKIA.

Generating a random image with specific content at each page reload/refresh

So I have a banner on a site, but I want to make it so that each time the page loads, a different image appears. More precisely, I want (say 50) squares (say having a black border, white fill) of random size (say from 5 pixels to 20 pixels in size) in random positions of a 750x63 px frame, with a white background.
What would be the best way to do this? I know a little JavaScript and HTML (and am very willing to learn more), but I really have no idea where to start. This is for my personal webpage, which I wish to spruce up a bit. Right now the fanciest code I have is some JavaScript for a simple Lightbox interface.
Wow, that was easier and more fun than I expected. Here's the code, that goes in the <body> section of my HTML code, optimized for a 750x80px frame. The random integer generator I got from this other question.
<canvas id="canvas" width="750" height="80"></canvas>
<script type="text/javascript">
//Random integer generator
function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
//Loop to make 80 squares
for (var i=0;i<80;i++) {
//The x-position of the square, with 5px padding in frame
var sx = getRandomInt(5,705);
//The y-position of the square, with 5px padding in frame
var sy = getRandomInt(5,35);
//The height of the square, smallest 8x8px, largest 40x40px
var sh = getRandomInt(8,40);
//First, create a black square
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect (sx, sy, sh, sh);
//Second, create a white square that's 4px shorter and thinner,
//leaving a boundary of 2px
ctx.fillStyle = "rgb(255,255,255)";
ctx.fillRect (sx+2, sy+2, sh-4, sh-4);
}
}
draw();
</script>
The approach I used is from a Mozilla Developers page. The result is something like this:
Hooray!

Resize as a function of distance between mc's

I hope this hasn't been asked too much before. When I search I only get questions pertaining to rescaling to window size.
Now my question. I got one space ship firing a beam against another ship. I want the beam to show for some time and I want it to "bridge" the two ships. In other words, I want the beam to extend its width between the two ships.
I try to do this with a dot movie clip that is 1 pixel wide and high (and aligned left edge). I try to resize it with the following code: (target is the ship to be fire at and owner is the ship firing)
dist.vx = target.x - owner.x;
dist.vy = target.y - owner.y;
dist.dist = Math.sqrt(dist.vx*dist.vx + dist.vy*dist.vy);
width = dist.dist;
x = owner.x;
y = owner.y;
rotation = Math.atan2(target.y-y, target.x-x)*180/Math.PI;
This doesn't work as intended because 1) dot also gets alot bigger in the other dimension - how can I "turn off" this behavior? and 2) sometimes it seems to get way to wide - but only in certain angles...
Any suggestions on either solving the heigh/width scaling or on another way to achieve the same effect?
(I'm new to coding and flash.) Thanks!
By resizing a dot, you will have a rectangle...
You can dynamically create a sprite covering both ships and moveTo the hit point of one ship then lineTo the other ship... You do not need distance calculation at all. What you have to do is being careful on the placement of the sprite. So that you can calculate relative hitting points by simple math.
Suppose you have mc space contining mc ship1 and mc ship2, and hit point coords on ships are named hx, hy and you will use sprite s, calculation will be as follows.
// calculate hit points relative to mc space
var s1HX:int = ship1.x + ship1.hx,
s1HY:int = ship1.y + ship1.hy,
s2HX:int = ship2.x + ship2.hx,
s2HY:int = ship2.y + ship2.hy,
// sprite relative moveTo lineTo coords will be these.
mX: int, mY: int,
lX: int, lY: int;
// top left of sprite will be minimum of the hit coords.
s.x = (s1HX <= s2HX)? s1HX : s2HX;
s.y = (s1HY <= s2HY)? s1HY : s2HY;
// now we can get sprite relative moveTo lineTo coordinates:
mX = s1HX - s.x;
mY = s1HY - s.y;
lX = s2HX - s.x;
lY = s2HY - s.y;
The rest is implementation with using these with fancy line styles etc...
To create a new sprite:
var s:Sprite = new Sprite();
Adding / removing it to/from mc space:
space.addChild(s);
space.removeChild(s);
For graphics use the graphics object of sprite.
s.graphics
For setting line styles you can use:
s.graphics.lineStyle(...) ,
s.graphics.lineBitmapStyle(...),
s.graphics.lineGradientStyle(...)
Functions, please read the manual for usage.
After setting the line style to draw the line use:
s.graphics.moveTo(mX,mY);
s.graphics.lineTo(lX,lY);
For pulsating effects you have to do a little more complicated things such as using tween class which you can read about here: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/fl/transitions/Tween.html
Note that:
Sprites are no complicated magic, they are like mc's but they do not have timelines etc.
Sprites try to scale when width or height change programmatically. So do not touch them, moveTo lineTo automatically sets the size of a sprite...

canvas isPointInPath does not work with ctx.drawImage()

I suppose this doesn't work because canvas is drawing a bitmap of a vector (and a bitmap is not a path).
Even if it did work, the bitmap is likely always has a rectangular permitter.
Is there any way to leverage something like isPointInPath when using drawImage?
example:
The top canvas is drawn using drawImage and isPointInPath does not work.
The bottom canvas is drawn using arc and isPointInPath works.
a link to my proof
** EDIT **
I draw a circle on one canvas, and use isPointInPath to see if the mouse pointer is inside the circle (bottom canvas in my example).
I also "copy" the bottom canvas to the top canvas using drawImage. Notice that isPointInPath will not work on the top canvas (most likely due to reasons I mentioned above). Is there a work-around I can use for this that will work for ANY kind of path (or bitmap)?
A canvas context has this hidden thing called the current path. ctx.beginPath, ctx.lineTo etc create this path.
When you call ctx.stroke() or ctx.fill() the canvas strokes or fills that path.
Even after it is stroked or filled, the path is still present in the context.
This path is the only thing that isPointInPath tests.
If you want to test if something is in an image you have drawn or a rectangle that was drawn with ctx.fillRect(), that is not possible using built in methods.
Typically you'd want to use a is-point-in-rectangle function that you write yourself (or get from someone else).
If you're looking for how to do pixel-perfect (instead of just the image rectangle) hit detection for an image there are various methods of doing that discussed here: Pixel perfect 2D mouse picking with Canvas
You could try reimplementing ctx.drawImage() to always draw a box behind the image itself, like so (JSFiddle example):
ctx.customDrawImage = function(image, x, y){
this.drawImage(image, x, y);
this.rect(x, y, image.width, image.height);
}
var img1 = new Image();
img1.onload = function(){
var x = y = 0;
ctx.drawImage(img1, x, y);
console.log(ctx.isPointInPath(x + 1, y + 1));
x = 1.25 * img1.width;
ctx.customDrawImage(img1, x, y);
console.log(ctx.isPointInPath(x + 1, y + 1));
Note: you might get side effects like the rectangle appearing over the image, or bleeding through from behind if you are not careful.
To me, isPointInPath failed after canvas was moved. So, I used:
mouseClientX -= gCanvasElement.offsetLeft;
mouseclientY -= gCanvasElement.offsetTop;
I had some more challenges, because my canvas element could be rescaled. So first when I draw the figures, in my case arc, I save them in an array together with a name and draw them:
if (this.coInit == false)
{
let co = new TempCO ();
co.name= sensor.Name;
co.path = new Path2D();
co.path.arc(c.X, c.Y, this.radius, 0, 2 * Math.PI);
this.coWithPath.push(co);
}
let coWP = this.coWithPath.find(c=>c.name == sensor.Name);
this.ctx.fillStyle = color;
this.ctx.fill(coWP.path);
Then in the mouse event, I loop over the items and check if the click event is in a path. But I also need to rescale the mouse coordinates according to the resized canvas:
getCursorPosition(event) {
const rect = this.ctx.canvas.getBoundingClientRect();
const x = ((event.clientX - rect.left ) / rect.width) * this.canvasWidth;
const y = ((event.clientY - rect.top) / rect.height) * this.canvasHeight;
this.coWithPath.forEach(c=>{
if (this.ctx.isPointInPath(c.path, x, y))
{
console.log("arc is hit", c);
//Switch light
}
});
}
So I get the current size of the canvas and rescale the point to the original size. Now it works!
This is how the TempCO looks like:
export class TempCO
{
path : Path2D;
name : string;
}

How to draw segment of a donut with HTML5 canvas?

As the title states. Is this possible?
Edit: When i say doughnut I mean a top, 2D view
Is the only option to draw a segment of a circle, then draw a segment of a smaller circle with the same origin and smaller radius over the top, with the colour of the background? That would be crap if so :(
You do it by making a single path with two arcs.
You draw one circle clockwise, then draw a second circle going counter-clockwise. I won't go into the detail of it, but the way paths are constructed knows to take this as a reason to un-fill that part of the path. For more detail of what its doing you can this wiki article.
The same would work if you were drawing a "framed" rectangle. You draw a box one way (clockwise), then draw the inner box the other way (counter-clockwise) to get the effect.
Here's the code for a doughnut:
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
// Pay attention to my last argument!
//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); // inner (unfills it)
ctx.fill();
Example:
http://jsfiddle.net/Hnw6a/
Drawing only a "segment" of it can be done by making the path smaller (you might need to use beziers instead of arc), or by using a clipping region. It really depends on how exactly you want a "segment"
Here's one example: http://jsfiddle.net/Hnw6a/8/
// half doughnut
ctx.beginPath()
ctx.arc(100,100,100,0,Math.PI, false); // outer (filled)
ctx.arc(100,100,55,Math.PI,Math.PI*2, true); // outer (unfills it)
ctx.fill();
You can make a 'top view doughnut' (circle with hollow center) by stroking an arc. You can see an example of this here: http://phrogz.net/tmp/connections.html
The circles (with nib) are drawn by lines 239-245:
ctx.lineWidth = half*0.2; // set a nice fat line width
var r = half*0.65; // calculate the radius
ctx.arc(0,0,r,0,Math.PI*2,false); // create the circle part of the path
// ... some commands for the nib
ctx.stroke(); // actually draw the path
Yes, I understand how old this question is :)
Here are my two cents:
(function(){
var annulus = function(centerX, centerY,
innerRadius, outerRadius,
startAngle, endAngle,
anticlockwise) {
var th1 = startAngle*Math.PI/180;
var th2 = endAngle*Math.PI/180;
var startOfOuterArcX = outerRadius*Math.cos(th2) + centerX;
var startOfOuterArcY = outerRadius*Math.sin(th2) + centerY;
this.beginPath();
this.arc(centerX, centerY, innerRadius, th1, th2, anticlockwise);
this.lineTo(startOfOuterArcX, startOfOuterArcY);
this.arc(centerX, centerY, outerRadius, th2, th1, !anticlockwise);
this.closePath();
}
CanvasRenderingContext2D.prototype.annulus = annulus;
})();
Which will add a function "annulus()" similar to "arc()" in the CanvasRenderingContext2D prototype. Making the closed path comes in handy if you want to check for point inclusion.
With this function, you could do something like:
var canvas = document.getElementById("canvas1");
var ctx = canvas.getContext("2d");
ctx.annulus(0, 0, 100, 200, 15, 45);
ctx.fill();
Or check this out: https://jsfiddle.net/rj2r0k1z/10/
Thanks!
With WebGL (one of the contexts of the HTML5 canvas) that is possible. There are even some JS libraries for browsers that don't support/implement it yet - check out these links:
http://sixrevisions.com/web-development/how-to-create-an-html5-3d-engine/
http://slides.html5rocks.com/#landing-slide
http://sebleedelisle.com/2009/09/simple-3d-in-html5-canvas/
http://www.khronos.org/webgl/
http://webdesign.about.com/od/html5tutorials/f/is-there-a-3d-context-for-html5-canvas.htm
http://code.google.com/p/html-gl/
Given the requirements, what #SimonSarris says satisfies the problem. But lets say you're like me and you instead want to "clear" a part of a shape that may be partially outside the bounds of the shape you're clearing. If you have that requirement, his solution won't get you want you want. It'll look like the "xor" in the image below.
The solution is to use context.globalCompositeOperation = 'destination-out' The blue is the first shape and the red is the second shape. As you can see, destination-out removes the section from the first shape. Here's some example code:
explosionCanvasCtx.fillStyle = "red"
drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
Here's the potential problem with this: The second fill() will clear everything underneath it, including the background. Sometimes you'll want to only clear the first shape but you still want to see the layers that are underneath it.
The solution to that is to draw this on a temporary canvas and then drawImage to draw the temporary canvas onto your main canvas. The code will look like this:
diameter = projectile.radius * 2
console.log "<canvas width='" + diameter + "' height='" + diameter + "'></canvas>"
explosionCanvas = $("<canvas width='" + diameter + "' height='" + diameter + "'></canvas>")
explosionCanvasCtx = explosionCanvas[0].getContext("2d")
explosionCanvasCtx.fillStyle = "red"
drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
durationPercent = (projectile.startDuration - projectile.duration) / projectile.startDuration
drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'source-over' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
ctx.drawImage(explosionCanvas[0], projectile.pos.x - projectile.radius, projectile.pos.y - projectile.radius) #center
Adapting/simplifying #Simon Sarris's answer to easily work with any angle gives the below:
To create an arc segment you draw an outer arc (of n radians) in one direction and then an opposite arc (of the same number of radians) at a smaller radius and fill in the resulting area.
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
var angle = (Math.PI*2)/8;
var outer_arc_radius = 100;
var inner_arc_radius = 50.;
ctx.beginPath()
//ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise);
ctx.arc(100,100,outer_arc_radius,0,angle, false); // outer (filled)
// the tip of the "pen is now at 0,100
ctx.arc(100,100,inner_arc_radius,angle,0, true); // outer (unfills it)
ctx.fill();
<canvas id="canvas1" width="200" height="200"></canvas>