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();
}
Related
I'm using the JavaScript canvas API for free drawing. I'm stuck at masking the area that is allowed to be drawn on - in my example it should only be the speechbubble area.
I'm using this Vue component: https://github.com/sametaylak/vue-draw/blob/master/src/components/CanvasDraw.vue
draw(event) {
this.drawCursor(event);
if (!this.isDrawing) return;
if (this.tools[this.selectedToolIdx].name === 'Eraser') {
this.canvasContext.globalCompositeOperation = 'destination-out';
} else {
this.canvasContext.globalCompositeOperation = 'source-over';
this.canvasContext.strokeStyle = this.tools[this.selectedToolIdx].color;
}
this.canvasContext.beginPath();
this.canvasContext.moveTo(this.lastX, this.lastY);
this.canvasContext.lineTo(event.offsetX, event.offsetY);
this.canvasContext.stroke();
[this.lastX, this.lastY] = [event.offsetX, event.offsetY];
},
drawCursor(event) {
this.cursorContext.beginPath();
this.cursorContext.ellipse(
event.offsetX, event.offsetY,
this.brushSize, this.brushSize,
Math.PI / 4, 0, 2 * Math.PI
);
this.cursorContext.stroke();
setTimeout(() => {
this.cursorContext.clearRect(0, 0, this.width, this.height);
}, 100);
},
There is a built-in clip() method which sets a path as the clipping region.
var ctx=document.getElementById("cnv").getContext("2d");
ctx.lineWidth=2;
ctx.strokeStyle="red";
ctx.moveTo(0,0);
ctx.lineTo(100,100);
ctx.stroke(); // 1.
ctx.strokeStyle="black";
ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(100,10);
ctx.lineTo(100,60);
ctx.lineTo(30,60);
ctx.lineTo(10,80);
ctx.closePath();
ctx.stroke(); // 2.
ctx.clip(); // 3.
ctx.strokeStyle="green";
ctx.beginPath();
ctx.moveTo(0,100);
ctx.lineTo(100,0);
ctx.stroke(); // 4.
<canvas id="cnv"></canvas>
red line is drawn between 0,0 and 100,100, without clipping
bubble is drawn in black
bubble is set as clipping region
green line is drawn between 0,100 and 100,0, and correctly clipped into the bubble.
In practice you may want to have the clipping region one pixel inside the bubble, so a separate path (which is not stroke()-d, just clip()-ped), so drawing can not modify the bubble itself. If you zoom in now as it is, you will see that the green line actually overdraws the inner pixels of the bubble (linewidth is 2 pixels, and the outer one is "unharmed").
Establishing if a given point belongs to polygon's area is a quite tricky and solved problem in Computer Science.
In this concrete scenario, where you have canvas with above image set as background and 500x300 dimension you don't really need to use ray casting algorithm.
You can for example divide speech bubble area to a rectangle and a triangle, and then using event.offsetX, event.offsetY check if any given point lies inside of any of these two figures.
Code example:
isPointInArea(event) {
const x = event.offsetX;
const y = event.offsetY;
// For rectangle it is straightforward
if (x >= 60 && x <= 325 && y >= 60 && y <= 215) {
return true;
}
/* Since two sides of this triangle are parallel to canvas
It is enough to check y coordinate with one linear function of a third one
in form of y = ax + b */
if(x >= 60 && x <= 120 && y >= 215) {
const boundaryY = -0.81818181818 * x + 313.181818182;
if (y <= boundaryY) {
return true;
}
}
return false;
}
In draw function of CanvasDraw.vue
draw(event) {
if(!this.isPointInArea(event)) {
return;
}
this.drawCursor(event);
if (!this.isDrawing) return;
...
Working example on codesandbox
Result:
Edit:
As pointed out by #tevemadar you can also use simply clip() method of Canvas API. If there is nothing else you want to render (And since it is not a game, so probably that is the case), then you can just execute clip() once and you are all set. Otherwise remember to use save() method (and then of course restore(), so that you can render stuff also outside of speech bubble clipping region.
I have been given the following task, but I am getting errors that can be seen when the code snippet is run. I would like some help figuring out what exactly I am doing wrong.
Basically, I need to draw a circle, make it so that it moves and changes the direction/color when touching the walls of the screen.
Task: create a Circle class with the following properties:
x - the initial value of the coordinate x
y is the initial value of the y coordinate
radius - values โโof width and height
color - fill color Describe the methods:
draw () - marks off on the screen an element that is described by the given properties
setColor (newColor) - Changes the fill color to newColor
move ({x = 0, y = 0}) - moves the captured object by the vector (x, y) - each time period (for example, 100 ms) changes (adds \ subtracts)
to the values โโx and y, respectively. When a circle collides with any
edge of the screen it is necessary to realize its mirror reflection
(change the value of the corresponding coordinate of the vector on the
opposite of the value of the sign, and call this method with the new
vector) and generate the collision event, collision, which is captured
at the document level.Hang on this event a handler that will change
the color of the pouring of the circle into another (random) value.
Movement occurs until the stop method is called.
stop () - stops the circle movement
If the Escape button on the keyboard was pressed, the movement should stop.
I created a canvas and set the frame to move. I drew a circle and tried to move it using setInterval(), but it seems like I'm losing the context.
let c = document.getElementById("mycanvas");
let ctx = c.getContext("2d");
let xinc = 1;
let yinc = 1;
class Circle {
constructor(xpos, ypos, radius, color) {
this.xpos = xpos;
this.ypos = ypos;
this.radius = radius;
this.color = color;
}
draw() {
ctx.beginPath();
ctx.arc(this.xpos, this.ypos, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "red";
ctx.fill();
}
move(xpos, ypos) {
ctx.clearRect(0, 0, c.width, c.height);
ctx.beginPath();
this.draw();
xpos += xinc;
ypos += yinc;
console.log(xpos, ypos);
if ((this.xpos > c.width - this.radius) || (this.xpos < 0 + this.radius)) {
xinc = -xinc;
}
if ((this.ypos > c.height - this.radius) || (this.ypos < 0 + this.radius)) {
yinc = -yinc;
}
setInterval(this.move, 10);
//this.draw();
}
}
let circle = new Circle(200, 300, 50, "red");
circle.draw();
circle.move(200, 300);
<canvas id="mycanvas" width="1335" height="650" style="border: 1px solid"> </canvas>
I am just starting to learn events and DOMs, please help me correctly implement this task
You are passing this.move to setInterval with no context - just a function, with no this to call it in. You can pass in this.move.bind(this) to create a bound function. You can also do it once in the constructor: this.move = this.move.bind(this).
Also, the call to beginPath in move seems unnecessary.
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();
I'm currently working on a little experimental project with HTML5 Canvas.
Basically, at the moment i'm just trying to make the canvas clear as expected. The only thing that my code does as the moment is generate a lines that are broken in the middle. However, at the moment i'm trying to make one line, and then remove that line and add another line at another different position without showing the first line.
I would of thought that this code would work:
poles(20); // this number is the x position at which the line (pole) will be generated
ctx.clearRect(0, 0, WIDTH, HEIGHT);
poles(140)
In all technically, this should show only the second pole because the canvas should have been cleared after the first pole was generated. But hit still shows both.
When I tried only:
poles(20);
ctx.clearRect(0, 0, WIDTH, HEIGHT);
The canvas was blank which told me that the clearing worked correctly.
I tried one more thing:
poles(20);
ctx.clearRect(0, 0, WIDTH, HEIGHT);
setTimeout(function () {
poles(140)
}, 1000);
In this case, both poles did show up but not until after 1 second which told me that the poles function is causing both to be generated again even though the function doesn't loop:
function poles(x) {
var bottomH = getRandomInt(20, 180)
// using seperate rectangles will make a break
rect(40, 220 - bottomH, x, 0); // first section of line
rect(40, bottomH, x, HEIGHT - bottomH); // second section of line
}
I hope someone can explain to me how come my poles function is cause both of the poles to reappear.
You can view the example here.
For Reference, the main code is:
var canvas = document.getElementById("canvas"),
WIDTH = canvas.width,
HEIGHT = canvas.height;
var ctx = canvas.getContext("2d");
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function rect(w, h, x, y) {
ctx.rect(x, y, w, h);
ctx.fill();
}
function poles(x) {
var bottomH = getRandomInt(20, 180); // determine where the line break will be
rect(40, 220 - bottomH, x, 0);
rect(40, bottomH, x, HEIGHT - bottomH);
}
poles(20);
ctx.clearRect(0, 0, WIDTH, HEIGHT);
setTimeout(function () {
poles(140)
}, 1000);
The problem lies with your rect function. Specifically, with the usage of the .rect() member of the ctx. The rect member creates a path, which you then fill with ctx.fill() - only problem is, it doesn't close the path, so it remains open and is added to when the second call to poles comes along.
You can either close the path before exiting the rect function, or even more simply, avoid paths altogether by defining and filling the rectangle in a single call, by using ctx.fillRect.
The following change makes the code function as expected:
function rect(w, h, x, y)
{
// ctx.rect(x, y, w, h);
// ctx.fill();
ctx.fillRect(x,y,w,h);
}
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();
}