How to fill custom shape has been draw in Canvas? - html

Im try to drew custom shape, but since I use moveTo ..I cant be filled it, so That my question is there's any way can may be determined points on screen to fill shape? or to do that I most use or drew another real shape in the same block as layer ...
Look at my example here to drew a simple shape:
to I can fill image with blue color Im drew a Fill rectangle, so That is is a true way?
Code for shape for before fill:
var canvas3 = document.getElementById('canvas3');
var c3 = canvas3.getContext('2d');
c3.fillStyle = 'green';
c3.beginPath();
c3.moveTo(10,30);
c3.lineTo(200,30);
c3.moveTo(10,80);
c3.lineTo(200,80);
c3.moveTo(10,30);
c3.lineTo(10,180);
c3.moveTo(200,30);
c3.lineTo(200,180);
//c3.closePath();
c3.fill();
c3.lineWidth = 5;
c3.strokeStyle = 'orange';
c3.stroke();
Code for shape after fill:
var canvas3 = document.getElementById('canvas3');
var c3 = canvas3.getContext('2d');
c3.fillStyle = 'blue';
c3.beginPath();
c3.moveTo(10,30);
c3.fillRect(10,30,190,60);
c3.moveTo(10,30);
c3.lineTo(10,180);
c3.moveTo(10,90);
c3.lineTo(200,90);
c3.moveTo(200,30);
c3.lineTo(200,180);
c3.moveTo(10,30);
c3.lineTo(200,30);
//c3.closePath();
c3.fill();
c3.lineWidth = 5;
c3.strokeStyle = 'orange';
c3.stroke();
and finally which is a best way to I can drew shapes like this?
Note: Im new on html5 canvas and I read from this online book.

is there any way can may be determined points on screen to fill
shape? or to do that I most use or drew another real shape in the same
block as layer
Just draw a shape in the same place. Fill first then stroke afterwards. A little planning may be required with canvas as to in which order to draw things.
You can define objects to hold the geometrical data if you plan to redraw often or move them around. This will certainly simplify the objective later on.
which is a best way to I can drew shapes like this?
In my opinion this code can be drawn much simpler and in fewer lines of codes. There is no need to break up a shape in several parts as in that code if you can draw a shape using a simple method for it. In this case four lines can be replaced with one rectangle.
Knowing how these shapes are drawn internally also helps so we can take advantage of the path a rect() leaves, i.e. closing in upper-left corner so we can continue from there.
For example:
var c3 = c.getContext("2d");
// fill blue box first as it will be at the bottom
c3.rect(10, 30, 190, 50); // x, y, width, height
c3.fillStyle = 'blue';
c3.fill();
// orange stroke
// we know rect() will close at upper-left corner so continue from there with left leg
c3.lineTo(10, 180);
c3.moveTo(200, 80); // create second leg at right separately
c3.lineTo(200, 180);
c3.strokeStyle = "orange";
c3.lineWidth = 5;
c3.lineJoin = c3.lineCap = "round";
c3.stroke();
<canvas id=c height=180></canvas>
An alternative approach would be to fill then build the line path:
var c3 = c.getContext("2d");
c3.fillStyle = 'blue';
c3.fillRect(10, 30, 190, 50); // fillRect does not add to path
// orange stroke
c3.moveTo(10, 180); // create "housing" starting at bottom-left corner
c3.lineTo(10, 30); // upper-left
c3.lineTo(200, 30); // upper-right
c3.lineTo(200, 180); // bottom-right
c3.moveTo(10, 80); // add cross-bar
c3.lineTo(200, 80);
c3.strokeStyle = "orange";
c3.lineWidth = 5;
c3.lineJoin = c3.lineCap = "round";
c3.stroke();
<canvas id=c height=180></canvas>

Related

Why are artifacts visible in a scaled html5 canvas?

I've seen this and this discussion about removing antialiasing in canvases, but I don't think this is the same thing.
After scaling an html5 canvas by an arbitrary value (i.e., making it responsive), I've noticed that if I draw two rectangles of the same size and in the same location, the edges of the scaled side of the first rectangle remain visible.
I've included an example snippet where I draw a grey rectangle, then draw an red rectangle on top of it. There's a one-pixel red vertical line on the left and right edges of the grey rectangle. I know it may seem trivial, but it's very noticeable in my situation.
How do I fix this? Thanks!
var example = document.getElementById("example");
var ctx = example.getContext('2d');
ctx.scale(1.13,1);
ctx.fillStyle = "LightGrey";
ctx.fillRect(10,10,50,30);
ctx.fillStyle = "Black";
ctx.font = "20px Arial";
ctx.fillText("< Looks good.",70,30);
ctx.fillStyle = "Red";
ctx.fillRect(10,50,50,30);
// This light grey rectangle should completely cover the previous red one, but it doesn't!
ctx.fillStyle = "LightGrey";
ctx.fillRect(10,50,50,30);
ctx.fillStyle = "Black";
ctx.font = "20px Arial";
ctx.fillText("< Do you see red?",70,70);
<canvas id="example"></canvas>
You are scaling the transform matrix by a factor of 1.13 on the X axis.
So your coordinate 10, will actually end up on at coordinate 11.3 on the real pixels matrix.
You can't draw on fraction of pixels, so indeed antialiasing will kick in here.
So why does the first one looks better?
Because the mix between grey and white* is more neutral than the one between red grey and white. But even your first rect is antialiased.
Just zoom in your canvas and you'll see it, there is a one pixel band on both sides that is actually semi-transparent.
* "White" here is the one of the page's background
var example = document.createElement("canvas");
var ctx = example.getContext('2d');
ctx.scale(1.13,1);
ctx.fillStyle = "LightGrey";
ctx.fillRect(10,10,50,30);
ctx.fillStyle = "Red";
ctx.fillRect(10,50,50,30);
ctx.fillStyle = "LightGrey";
ctx.fillRect(10,50,50,30);
// draw bigger with no antialiasing
var z_ctx = zoomed.getContext('2d');
zoomed.width = example.width * 10;
zoomed.height = example.height * 10;
z_ctx.imageSmoothingEnabled = false;
z_ctx.drawImage(example, 0,0, zoomed.width, zoomed.height);
<canvas id="zoomed"></canvas>
So how to avoid this?
Well simply avoid filling at non integer pixel coordinates. This means you have to be constantly aware of your context transformation matrix too, not only of the values you pass to the drawing functions.
(Ps: also remember that stroke is an even eviler beast since it start drawing from the middle of the line, so in this case, you even have to take into considerations the lineWidth, see this Q/A on the matter).

HTML canvas: Why does a large shadow blur not show up for small objects?

Here's a demonstration:
var ctx = document.getElementById("test").getContext("2d");
ctx.shadowColor = "black";
ctx.fillStyle = "white";
ctx.shadowBlur = 10;
ctx.fillRect(10, 10, 10, 10);
ctx.shadowBlur = 50;
ctx.fillRect(70, 10, 10, 10);
ctx.fillRect(70, 70, 70, 70);
<canvas id="test" width="200" height="200"></canvas>
If I set shadowBlur=10 and then draw a small 10x10 square, I get a nice, strong shadow. The same if I set shadowBlur=50 and draw a big 70x70 square. But if I set shadowBlur=50 and then draw a small 10x10 square, I get a very faint, barely visible shadow.
Instead I would have expected a small center square and a large dark shadow all around it.
Obviously I misunderstand how the shadow blur works, so - how does it work, and how do I get a large dark shadow around a small object?
The shadowBlur uses Gaussian blur to produce the shadow internally. The object is drawn to a separate bitmap as stencil in the shadow-color and then blurred using the radius. It does not use the original shape after this step. The result is composited back (as a side-note: there was previously a disagreement on how to composite shadows so Firefox and Chrome/Opera rendered them differently - I think they have landed on source-over in both camps by now though).
If the object is very small and the blur radius very big, the averaging will be thinned by the empty remaining space around the object leaving a more faint shadow.
The only way to get a more visible shadow with the built-in method is to use a smaller radius. You can also "cheat" using a radial gradient, or draw a bigger object with shadow applied to an off-screen canvas but offset relative to the shadow itself so the object doesn't overlap it, then draw the shadow only (using clipping arguments with drawImage()) back to main canvas at desired size before drawing main object.
In newer versions of the browsers you can also produce Gaussian blurred shadows manually using the new filter property on the context with CSS filters. It do require some extra compositing steps and most likely an off-screen canvas for most scenarios, but you can with this method overdraw shadows in multiple steps with variable radii from small to bigger producing a more pronounced shadow at the cost of some performance.
Example of manually generated shadow using filter:
This allow for more complex shapes like with the built-in shadow, but offer more control of the end result. "Falloff" in this case can be controlled by using a easing-function with an initial normalized radius value inside the loop.
// note: requires filter support on context
var ctx = c.getContext("2d");
var iterations = 16, radius = 50,
step = radius / iterations;
for(var i = 1; i < iterations; i++) {
ctx.filter = "blur(" + (step * i) + "px)";
ctx.fillRect(100, 50, 10, 10);
}
ctx.filter = "none";
ctx.fillStyle = "#fff";
ctx.fillRect(100, 50, 10, 10);
<canvas id=c></canvas>
Example of gradient + filter:
This is a more cross-browser friendly solutions as if filter is not supported, at least the gradient comes close to an acceptable shadow. The only drawback is it is more limited in regards to complex shapes.
Additionally, using a variable center point for the gradient allows for mimicking fall-off, light size, light type etc.
Based on #Kaiido's example/mod in comment -
// note: requires filter support on context
var ctx = c.getContext("2d");
var grad = ctx.createRadialGradient(105,55,50,105,55,0);
grad.addColorStop(0,"transparent");
grad.addColorStop(0.33,"rgba(0,0,0,0.5)"); // extra point to control "fall-off"
grad.addColorStop(1,"black");
ctx.fillStyle = grad;
ctx.filter = "blur(10px)";
ctx.fillRect(0, 0, 300, 150);
ctx.filter = "none";
ctx.fillStyle = "#fff";
ctx.fillRect(100, 50, 10, 10);
<canvas id=c></canvas>

How can I draw a line with lineTo in HTML5 that has angles to match the Unit Circle?

If I have this,
var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = '#f00';
ctx.beginPath();
ctx.moveTo(25, 25);
ctx.lineTo(150, 25);
ctx.rotate(Math.PI*7/4); //315 degrees
ctx.lineTo(150,90);
ctx.stroke();
It does not draw the line (the ctx.lineTo(150,90);) at the angle I thought it would which is from the end of the first lineTo at 25,25 to a 150,90 at a 45 degree angle. If I use -Math.PI*7/4 I get what looks like a 45 degree angle, but it points the wrong way. Math.PI*5/4 goes the wrong way or rotation.
My question asks about the Unit Circle. I don't really need them all at once, just the ability to know how to draw them if I need them.
The canvas is rotating this way:
Consider the following example, without the y offset:
Given by the code:
var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = '#f00';
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(250, 0);
ctx.rotate(45*Math.PI/180); //45 degrees
ctx.lineTo(150,0);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0,0);
ctx.strokeStyle = '#ff0000';
ctx.lineTo(350,0);
ctx.font = "30px Arial";
ctx.fillText("New x axis",100,50);
ctx.stroke();
The red line is the new x axis, and the black line touches it exactly at a distance of 150 from the origin.
Now overlaying the same red line with your coordinates (and rotating by 45 degrees, so that it doesn't go off screen), we get
The black line ends at a point which is indeed (150,90), however at the new coordinate system. JSFiddle here
If you want the angle between the two line segments to be exactly what you pass in rotate(...), then keep drawing "horizontal" lines, but translate the origin to the end of each line segment, so you rotate about this point, not the top-left corner, using ctx.translate(<current point>). For example: https://jsfiddle.net/Douma37/65z1p38z/ (this goes off canvas, but has minimal interferance with your code).
Hope that helps!

In canvas the alpha value of a stroke gets lost after another one

I'm creating a drawing program witch should also use semi-transparent brushes. When I use a transparent brush I end up with some transparent strokes, witch are the lasts until I release the mouse. If I then draw a new stroke again my old strokes get full opacity, even if I don't come across them. The program works getting mouse coordinates, waiting for position changed, and then draws (and strokes) a line which goes from the first point to the second. I have seen that some tutorial suggests to store in memory (array) all the path and draw it again on every mouse release, but I'm not sure due to memory consumption. The program is written in QML + javascript, but canvas works in the same way as does in HTML5.
Thank you in advance to everybody.
The following is the context call:
function pencilBehaviour() {
if (canvas.isPressed){
var ctx = canvas.getContext('2d')
if ((canvas.bufferX != -1) || (canvas.bufferY != -1)){
ctx.globalCompositeOperation = "source-atop"
ctx.moveTo(canvas.bufferX, canvas.bufferY)
ctx.lineTo(canvas.px, canvas.py)
ctx.globalAlpha = 0.4
ctx.lineCap = "round"
ctx.lineJoin = "round"
ctx.strokeStyle = "white"
ctx.lineWidth = 3
ctx.stroke()
console.log("pencil invoking canvas")
//Buffers are needed to draw a line from buffer to current position
canvas.bufferX = canvas.px
canvas.bufferY = canvas.py
}
else{
//Buffers are needed to draw a line from buffer to current position
canvas.bufferX = canvas.px
canvas.bufferY = canvas.py
}
}
}
Hard to know without code, but here is a guess...
Make sure all your new strokes begin with context.beginPath() so the context is not "remembering" your previous strokes.

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>