Fill polygon on canvas - javascript

How can I fill the path that I've drawn in red?
http://jsfiddle.net/MVXZu/1/
I've tried to use fill but it doesn't fill my path as I want it to - that is to fill in the red outline - but instead it fills only a diagonal portion (comment out the ctx.fill(); to see the full outline I want to fill) The code that's drawing the line is this:
//loop through the data
ctx.beginPath();
for (var i = 0; i < data.length; i++) {
ctx.lineWidth=3;
ctx.lineCap = 'round';
ctx.strokeStyle = 'red';
ctx.moveTo(linePosX,linePosY);
ctx.lineTo((i*cellWidth) + cellWidth + padding,(tableHeight + padding) - data[i].v);
linePosX = i*cellWidth + padding + cellWidth;
linePosY = (tableHeight + padding) - data[i].v;
if(i == 13) {
ctx.lineTo(linePosX,tableHeight+padding);
ctx.lineTo(padding,tableHeight+padding);
}
ctx.fillStyle="red";
ctx.fill();
ctx.stroke();
ctx.closePath();
}

Don't put the beginPath into the loop
Don't use moveTo, as that creates a new polygon to fill (see it here, as the result of closePath()). You're at the correct position after the before lineTo anyway. Without it it works better…
Here's the fixed fiddle: http://jsfiddle.net/MVXZu/3/
Pseudo-code:
ctx.beginPath();
// set ctx styles
ctx.moveTo( bottom-left corner );
for each (point in data) {
ctx.lineTo(point);
}
ctx.lineTo( bottom-right corner );
ctx.closePath(); // automatically moves back to bottom left corner
ctx.fill();

Hopefully this is what you were wanting: http://jsfiddle.net/MVXZu/2/
Because you were only running some of the code if (i == 13) {}, only the moveTo and the first lineTo were getting called. This was resulting in a line at the top of the section, but not a box to fill.
I moved a lot of the drawing outside of the loop. It creates the first point in the bottom left of the graph, then the points along the top, then the point in the bottom right. Then it fills in the whole graph after the path is finished.

Related

Hide a canvas ctx.rect() after 1 second - Javascript

I have a canvas which is filled with the webcam stream.
On top of that, I want to have rectangles (just the borders of a rectangle) appear for 1 second at random areas. So every second a rectangle will pop up, and the next it will be somewhere else.
Currently, rectangles are appearing every second but the last doesn't disappear. Therefore, on the 2nd second there are 2 rectangle, 3rd second 3 rectangles, etc...
I need to find a way to either have the rectangle appear for 1 second, have it removed after 1 second, or have it moved after 1 second: results are the same for me.
let sx; // x axis
let sy; // y axis
let i = setInterval( axisChanger, 1000 ); // pops up every second
function axisChanger() {
sx = getRandomInt(0, 535); // gets a random num
sy = getRandomInt(0, 445); // gets a random num
}
requestAnimationFrame(animate);
function animate(t) {
requestAnimationFrame(animate);
randomRect();
}
function randomRect() {
ctx.rect(sx, sy, 50, 30); // these 4 lines make a hollow rectangle: border only.
ctx.lineWidth = 2;
ctx.strokeStyle = '#FF0000';
ctx.stroke();
}
If I use clearRect(), then the inside of the rectangle will also be gone... and so part of the webcam stream with it.
If you only need to draw a single rectangle, replace rect() and stroke() with strokeRect():
function randomRect() {
ctx.lineWidth = 2;
ctx.strokeStyle = '#FF0000';
ctx.strokeRect(sx, sy, 50, 50);
}
The reason for the current behavior is that rect() adds to the main path and accumulates all rect() calls. Because of that the path must be cleared using beginPath().
But since you are only using a single rectangle you can simply use strokeRect() which does not add anything to the path but renders directly.
The alternative however, would be:
function randomRect() {
ctx.beginPath(); // clear path and sub-paths
ctx.rect(sx, sy, 50, 30); // these 4 lines make a hollow rectangle: border only.
ctx.lineWidth = 2;
ctx.strokeStyle = '#FF0000';
ctx.stroke();
}

Canvas special shape - animating

I'm finishing a project, but I have one more step to finish.
I want to visualize microphone input by a canvas.
Getting the data from the microphone isn't a problem.
But I want to visualize it in a special way. (see image)
I want to animate each element from the wave.
My problem isn't the animation.
My problem is to create those shapes in the CANVAS.
This is an example of one shape:
I can create a rounded corner shape with the canvas
const draw = () => {
fillRoundedRect(20, 20, 100, 100, 20);
ctx.fillStyle = "red";
ctx.fill();
};
const fillRoundedRect = (x, y, w, h, r) => {
ctx.beginPath();
ctx.moveTo(x+r, y);
ctx.lineTo(x+w-r, y);
ctx.quadraticCurveTo(x+w, y, x+w, y+r);
ctx.lineTo(x+w, y+h-r);
ctx.quadraticCurveTo(x+w, y+h, x+w-r, y+h);
ctx.lineTo(x+r, y+h);
ctx.quadraticCurveTo(x, y+h, x, y+h-r);
ctx.lineTo(x, y+r);
ctx.quadraticCurveTo(x, y, x+r, y);
ctx.fill();
};
Can someone help me with creating a shape like in the second image?
Thanks in advance guys!
Instead of trying to make a single shape with dependency on surrounding shapes and a high risk of headache math-wise, use instead two shapes which you merge using composition. My suggestion anyways.
Draw all the bars in full height using composition mode source-over (default)
Define a single shape on top using some sort of spline (I would suggest a cardinal spline).
Set composition mode to destination-out and render an enclosed shape using the spline as top "line".
Example
This should work in a loop (remember to clear canvas for each frame) but shows only the building stones needed here -
var ctx = c.getContext("2d");
var points = [];
var skippy = 0;
// render all bars
ctx.globalCompositeOperation = "source-over"; // not needed here, but in a loop yes!
// produce bars
ctx.beginPath(); // not needed here, but in a loop yes!
for(var x = 0; x < c.width; x += 30) {
ctx.rect(x, 0, 16, c.height)
// OKIDOKI, lets produce the spline using random points (y) as well
// but not for all, only every second for prettyness... modify to taste
if (skippy++ % 2 === 0) points.push(x, c.height * Math.random());
}
points.push(c.width, c.height * Math.random()); // one last
ctx.fillStyle = "rgb(198, 198, 198)";
ctx.fill();
// render spline
ctx.beginPath();
ctx.moveTo(0, c.height); // bottom left corner
curve(ctx, points); // spline
ctx.lineTo(c.width, c.height); // bottom right corner
ctx.closePath();
ctx.globalCompositeOperation = "destination-out";
ctx.fill();

Canvas fill shape with text

I have this example in canvas. I would like to know how I can add text in shapes.
I have looked into the code that draws shapes but not really sure how to add text. I was able to add text but it doesn't move with balls?
function draw()
{
context.clearRect(0, 0, stageWidth, stageHeight);
var i = balls.length;
while(--i > -1)
{
context.fillStyle = balls[i].color;
context.beginPath();
context.arc(balls[i].x,balls[i].y,balls[i].size,0,Math.PI*2,true);
context.closePath();
context.fill();
}
}
You just have to use the ball's x and y value to make sure the text follow the ball, and use textAlign and textBaseline to easily align the text.
function draw()
{
context.clearRect(0, 0, stageWidth, stageHeight);
// Align once an for all.
context.textAlign = 'center'; // Set text align to center.
context.textBaseline = 'middle'; // Set text vertical align to middle.
var i = balls.length;
while(--i > -1)
{
context.fillStyle = balls[i].color;
context.beginPath();
context.arc(balls[i].x,balls[i].y,balls[i].size,0,Math.PI*2,true);
context.closePath();
context.fill();
// Easier to see the text.
context.fillStyle = 'black';
// Draw text `Ball # i` centered at position (x, y),
// with the width of the text equal to ball's size * 2.
context.fillText('Ball #' + i, balls[i].x ,balls[i].y, balls[i].size * 2);
}
}
See altered jsfiddle, is that what you want?
P.S : When playing with canvas, it's always best to keep HTML5 Canvas Cheat Sheet handy.

Drawing lines with canvas by using for loop

I am trying to draw lines with canvas and I am changing the coordinates with a for loop.
here is my canvas element:
<canvas id="c" width="300px" height="300px"></canvas>
and here is the js codes:
var c = document.getElementById('c');
ci = c.getContext('2d');
for(var a = 18; a < 300; a +=18){
fnc(a, ci);
}
function fnc(x, ci){
ci.strokeStyle = 'red';
ci.moveTo(0, x);
ci.lineTo(300, x); ci.lineWidth = 0.2; ci.stroke();
}
As you can see I am trying to draw these lines with 18px spaces between them. But the thickness of the lines and the color(or opacity, I am not sure) are changing from top to bottom.
Here is a fiddle : http://jsfiddle.net/J6zzD/1/
So what is wrong with that I can't find my mistake. Why are the color and the thicknesses are different?
UPDATE :
I just wrote these lines out of the function and now all the lines becomes faded but thicknesses are same. So strange :
ci.strokeStyle = 'red';
ci.lineWidth = 0.2; ci.stroke();
here is demo : http://jsfiddle.net/J6zzD/4/
That's again the eternal issue of forgetting to call beginPath.
Each time you call moveTo then lineTo, you create a new *sub*path, which adds to the current Path.
Then each time you call stroke(), the current path, so all the current subpaths get re-drawn, when the last added path is drawn for the first time.
Since opacities will add-up, top lines will reach 100% opacity (alpha=255) when the bottom line, drawn once, will have a 20% opacity (lineWidth=0.2) .
In your second fiddle, you stroke only once, so all lines have 20% opacity, which is correct for the 0.2 lineWidth.
So : use beginPath before drawing a new figure.
In this case you have two choices :
• draw line by line
OR
• draw once a path with all lines as subpath.
(see code below).
TIP : To get clean lines remember that pixels's center is at the (+0.5, +0.5) coordinates of each pixels, so
a 'trick' is to translate by 0.5, 0.5 on app start, then only use rounded coordinates and lineWidth.
1) draw line by line
http://jsfiddle.net/gamealchemist/J6zzD/6/
var c = document.getElementById('c');
var ctx = c.getContext('2d');
ctx.translate(0.5, 0.5);
ctx.lineWidth = 1;
for (var y = 18; y < 300; y += 18) {
strokeLine(ctx, y);
}
function strokeLine(ctx, y) {
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.moveTo(0, y);
ctx.lineTo(300, y);
ctx.stroke();
}
2) draw multiple subPath :
(you can have only one color for one stroke() )
http://jsfiddle.net/gamealchemist/J6zzD/7/
var c = document.getElementById('c');
var ctx = c.getContext('2d');
ctx.translate(0.5, 0.5);
ctx.lineWidth = 1;
ctx.strokeStyle = 'red';
ctx.beginPath();
for (var y = 18; y < 300; y += 18) {
addLineSubPath(ctx, y);
}
ctx.stroke();
function addLineSubPath(ctx, y) {
ctx.moveTo(0, y);
ctx.lineTo(300, y);
}
See: https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Applying_styles_and_colors#A_lineWidth_example
Because canvas coordinates do not directly reference pixels, special care must be taken to obtain crisp horizontal and vertical lines.
Basically, because you're trying to draw a line that's 0.2 pixels wide, the browser does some math to approximate a continuous number into discrete units and you get your "fading" lines.
So now we can fix up your code by changing context.lineWidth to 1 (I actually remove it because it defaults to 1) and shifting everything down by half a pixel.
var c = document.getElementById('c');
ci = c.getContext('2d');
for(var a = 18.5; a < 300.5; a +=18)
{
fnc(a, ci);
}
function fnc(x, ci)
{
ci.strokeStyle = 'red';
ci.moveTo(0, x);
ci.lineTo(300, x);
ci.stroke();
}
Demo

HTML5: Antialiasing leaves traces when I erase the image

I want to move a widget around on the canvas, and for various reasons I don't want to use sprites. I'm using the latest version of Chrome. In order to move the widget, I 'undraw' it and then redraw it in another place. By 'undraw', I mean that I just draw the same image in the same place, but draw it with the same color as the background, so the widget disappears completely before I draw the new one. The problem is that when I 'undraw', traces of the original image remain on the canvas. I've poked around on related questions here and haven't found anything that helps. I understand the problem of drawing a one-pixel line and getting anti-aliasing, so I set my line width to 2 (and various other non-integer values), but to no avail. Anyone have any ideas? Here's a fiddle demo, and here's the function that does the update:
function draw(){
if(previousX !== null) {
ctx.lineWidth = 1;
ctx.fillStyle = '#ffffff';
ctx.strokeStyle = '#ffffff';
drawCircle(previousX, previousY, 20);
}
ctx.lineWidth = 1;
ctx.fillStyle = '#000000';
ctx.strokeStyle = '#000000';
drawCircle(x, y, 20);
console.log('drew circle (' + x + ', ' + y + ')');
previousX = x;
previousY = y;
}
P.S. I'm just a hobbyist with no great experience in graphics, so please dumb-down your answer a bit if possible.
When your draw a shape with anti-aliasing, you are doing a solid covering of some pixels, but only a partial covering of the edge pixels. The trouble is that pixels (temporarily ignoring LCD panels) are indivisible units. So how do we partially cover pixels? We achieve this using the alpha channel.
The alpha channel (and alpha blending) combines the colour at the edge of a circle with the colour underneath it. This happens when the circle only partially covers the pixel. Here's a quick diagram to visualise this issue.
The mixing of colours causes a permanent change that is not undone by drawing the circle again in the background colour. The reason: colour mixing happens again, but that just causes the effect to soften.
In short, redrawing only covers up the pixels with total coverage. The edge pixels are not completely part of the circle, so you cannot cover up the edge effects.
If you need to erase the circle, rather think about it in terms of restoring what was originally there. You can probably copy the original content, then draw the circle, then when you want to move the circle, restore the original content and repeat the process.
This previous SO question may give you some ideas about copying canvas regions. It uses the drawImage method. The best solution would combine the getImageData and putImageData methods. I have modified your Fiddle example to show you how you might do this. You could try the following code:
var x, y, vx, vy;
var previousX = null, previousY = null;
var data = null;
function draw(){
ctx.lineWidth = 2.5;
ctx.fillStyle = '#000000';
ctx.strokeStyle = '#FF0000';
drawCircle(x, y, 20);
previousX = x;
previousY = y;
}
function drawCircle(x, y, r){
// Step 3: Replace the stuff that was underneath the previous circle
if (data != null)
{
ctx.putImageData(data, previousX - r-5, previousY - r-5);
}
// Step 1: Copy the region in which we intend to draw a circle
data = ctx.getImageData(x - r-5, y - r-5, 2 * r + 10, 2 * r + 10);
// Step 2: Draw the circle
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2, true);
ctx.closePath();
ctx.stroke();
ctx.fill();
}

Categories

Resources