Canvas fillRect() stops working after two iterations [duplicate] - javascript

This question already has answers here:
Get canvas width and height using javascript
(2 answers)
Closed 1 year ago.
I saw an interesting creative maths plotting script using the modulo of the result of powers and wanted to experiment with it, changing the modulo iteratively.
Here we're drawing a 256x256 pattern and then after an interval changing the modulo value and redrawing (for simplicity just toggling the modulo between two values in an attempt to debug the problem).
It only draws two iterations before apparently the Canvas stops updating and we get stuck on the second pattern. I thought context.clearRect() might solve it but it makes no difference. The function keeps running because I get the console.debug() output but nothing updates visually.
I have the same result in both Chrome and Safari
What am I doing wrong?
https://jsbin.com/zacoperuho/edit?html,console,output
<canvas id="container"></canvas>
<script>
container.width = 1024;
container.height = 1024;
const context = container.getContext("2d");
var modulo = 9;
function draw(){
context.clearRect(0, 0, context.width, context.height);
console.debug("modulo " + modulo)
for (let x = 0; x < 256; x++) {
for (let y = 0; y < 256; y++) {
if ((x ^ y) % modulo) {
context.fillRect(x*4, y*4, 4, 4);
}
}
}
if(modulo == 9){
modulo = 7;
} else {
modulo = 9;
}
}
draw();
setInterval(draw, 5000);
</script>

You're right, you need to clear the canvas's context and context.clearRect is the regular way to do it.
The problem are the parameters you're feeding it: context.width, context.height
The constantcontext is set to getContext("2d") thus it's an instance of
CanvasRenderingContext2D. This interface doesn't offer any properties called width or height. Instead you need to query the width & height on the Canvas element itself.
So simply change
context.clearRect(0, 0, context.width, context.height);
to
context.clearRect(0, 0, container.width, container.height);

Related

Exponential Graph Animation P5js Canvas

I am trying to animate a growing exponential graph using P5js.
I have successfully plotted the graph itself, but the "rulers/scales" on the sides won't work.
I want the "window" to scale according to the X and Y axis, just like this example: Animation I am trying to replicate this animation
I want the graph to "grow" and the rulers/scales on the sides to represent the growth, X is time and Y the multiplier (big text in the middle). As seen on the animation I linked, X and Y values move towards the origin after the graph has gone outside the box.
Link to P5 editor with code: P5 web editor
There is at least one big error in
scaleLevel -= 0.1;
because this way it gets zero and you will divide by it within REscale.
Your intention is to draw some exponential function f(x) in the interval 0 to x. The value of x is increasing by time. The value of the exponential function is also rising but with another rate. So you will have to use two separate scale factors: sX = display_width / x and sY = display_hight / f(x).
I hope this gets you started somehow.
Here is some code to illustrate which way to go:
var x = 10
function setup() {
createCanvas(400, 400);
noLoop();
}
function my_func(x) {
return exp(x * 0.2);
}
function draw() {
background(220);
stroke(155);
strokeWeight(8);
noFill();
beginShape();
let nPoints = 20;
let dx = x / nPoints;
let ymax = my_func(x);
let dy = ymax / nPoints;
for (let i = 0; i <= x; i += dx) {
xValue = map(i, 0, x, 0, width);
yValue = map(my_func(i), 0, ymax, height, 0);
vertex(xValue, yValue);
}
endShape();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.js"></script>
I omitted the ticks on the axes. I decided to create a static plot for the value of x in the between 0 and 10. The code can easily be changed into an animation by removing the noLoop(); statement in the setup function and adding the line x += somedelta; within the draw function.

How can I make my JavaScript programs run faster?

Ok, so I am working on a sort of detection system where I will be pointing the camera at a screen, and it will have to find the red object. I can successfully do this, with pictures, but the problem is that it takes several seconds to load. I want to be able to do this to live videos, so I need it to find the object immediately. Here is my code:
video.addEventListener('pause', function () {
let reds = [];
for(x=0; x<= canvas.width; x++){
for(y=0; y<= canvas.height; y++){
let data = ctx.getImageData(x, y, 1, 1).data;
let rgb = [ data[0], data[1], data[2] ];
if (rgb[0] >= rgb[1] && rgb[0] >=rgb[2] && !(rgb[0]>100 && rgb[1]>100 && rgb[2]>100) && rgb[1]<100 && rgb[2]<100 && rgb[0]>150){
reds[reds.length] = [x, y]
}
let addedx = 0
let addedy = 0
for(i=0; i<reds.length; i++){
addedx = addedx + reds[i][0]
addedy = addedy + reds[i][1]
}
let center = [addedx/reds.length, addedy/reds.length]
ctx.rect(center[0]-5, center[1]-5, 10, 10)
ctx.stroke()
}, 0);
Ya, I know its messy. Is there something about the for loops that are slow? I know I'm looping through thousands of pixels but that's the only way I can think of to do it.
As it has been said, Javascript is not the most performant for this task. However, here are some things I noticed, which could slow you down.
You grab the image data one pixel at a time. Since this method can return the whole frame, you can do this once.
Optimize your isRed condition:
rgb[0] >= rgb[1] && // \
rgb[0] >= rgb[2] && // >-- This is useless
!(rgb[0] > 100 && rgb[1] > 100 && rgb[2] > 100) && // /
rgb[1] < 100 && // \
rgb[2] < 100 && // >-- These 3 conditions imply the others
rgb[0] > 150 // /
You calculate the center inside your for loop after each pixel, but it would only make sense after processing the whole frame.
Since the video feed is coming from a camera, maybe you don't need to look at every single pixel. Maybe every 5 pixels is enough? That's what the example below does. Tweak this.
Demo including these optimizations
Node: This demo includes an adaptation of the code from this answer, to copy the video onto the canvas.
const video = document.getElementById("video"),
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
let width,
height;
// To make this demo work
video.crossOrigin = "Anonymous";
// Set canvas to video size when known
video.addEventListener("loadedmetadata", function() {
width = canvas.width = video.videoWidth;
height = canvas.height = video.videoHeight;
});
video.addEventListener("play", function() {
const $this = this; // Cache
(function loop() {
if (!$this.paused && !$this.ended) {
ctx.drawImage($this, 0, 0);
const reds = [],
data = ctx.getImageData(0, 0, width, height).data,
len = data.length;
for (let i = 0; i < len; i += 5 * 4) { // 4 because data is made of RGBA values
const rgb = data.slice(i, i + 3);
if (rgb[0] > 150 && rgb[1] < 100 && rgb[2] < 100) {
reds.push([i / 4 % width, Math.floor(i / 4 / width)]); // Get [x,y] from i
}
}
if (reds.length) { // Can't divide by 0
const sums = reds.reduce(function (res, point) {
return [res[0] + point[0], res[1] + point[1]];
}, [0,0]);
const center = [
Math.round(sums[0] / reds.length),
Math.round(sums[1] / reds.length)
];
ctx.strokeStyle = "blue";
ctx.lineWidth = 10;
ctx.beginPath();
ctx.rect(center[0] - 5, center[1] - 5, 10, 10);
ctx.stroke();
}
setTimeout(loop, 1000 / 30); // Drawing at 30fps
}
})();
}, 0);
video, canvas { width: 250px; height: 180px; background: #eee; }
<video id="video" src="https://shrt-statics.s3.eu-west-3.amazonaws.com/redball.mp4" controls></video>
<canvas id="canvas"></canvas>
I would run the detection algorithm in a webassembly module. Since it is just pixel data, thats right up its alley.
You could then pass individual frames to a different instance of the wasm module.
As far as answering your question directly, I would grab the whole frame, not 1 pixel at a time, or you might get pixels sampled from different frames. You can then submit that frame to a worker, you could even divide up the frame and send them to different workers (or as previously mentioned a wasm module)
Also since you have an array you can use Arrray.map and Array.reduce to get you to just the red values, and how big they are by testing for adjacent pixels, instead of all the comparison. Not sure if it will be faster but worth a try.
For the speed, you should consider all your process:
more your language is near the machine language, better your result will be. Saying so, C++ is better for the algorithm.
CPU speed is your friend. Launching your code on an Atom processor or on an i7 processor, is like night and day. Moreover, some type of processor is dedicated for vision like VPU
For your code:
You try to rewrite code that already exists. You can find good examples of detection in the great OpenCV library: https://www.learnopencv.com/invisibility-cloak-using-color-detection-and-segmentation-with-opencv
Hope it help you :)

Simple canvas animation: 50x50 image moving across

I've done this sort of programming before but It was a long while back, and despite trying for a while now, I am unable to get this working. I've tried loads of other similar codes that I've found on the internet but they don't work exactly the way I want it to! I basically want a 155x55 canvas, with a 50x50 image moving across it, simple! Despite how simple it sounds... I'm struggling... I've tried adapting my previous code but that was for bouncing balls and it was a long time ago. I'll appreciate any help. Thanks!
var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
var speed = 1;
a = new Image();
a.src = "http://www.animated-gifs.eu/category_cartoons/avatars-100x100-cartoons-spongebob/0038.gif";
function frameRate(fps) {
timer = window.setInterval( updateCanvas, 1000/fps );
}
function updateCanvas() {
ctx.fillRect(0,0, myCanvas.width, myCanvas.height);
draw();
}
function draw() {
/* Add code here to randomly add or subtract small values
* from x and y, and then draw a circle centered on (x,y).
*/
var x = 0 + speed;
var y = 20;
if (x > 150) {
x == 1;
}
ctx.beginPath();
ctx.drawImage(a,x,y,100,100);
}
/* Begin animation */
frameRate(25);
Fiddle Link:
https://jsfiddle.net/th6fcdr1/
The problem you have is that your variable x and y are always reset to 0 and 20. Your speed is 1 so your x is always 1.
Since you never update the x position and always reset it to 0. What you could do is to increase the variable speed by 1 at the end of the frame.
speed += 1
At first, you'll have:
x = 0 + 1
then
x = 0 + 2
... and so on.
Then you'll have to check for speed being above 150 and reset speed to 1.
Then I suggest renaming speed by posX which is more accurate. Also, instead of using setInterval you should be using requestAnimationFrame(). And instead of incrementing the posX by 1, you should be incrementing the posX by speed * elapsedTime to get a fluent move and stable speed move which doesn't depend on the framerate.
In the end, you'd have this:
posX += speed * elapsedTime
var x = posX

Very laggy animation in javascript

I have made a board with 156X64 divs 3 pixel each with border radius, so it looks like a board out of LED. I have string representing 0 or 1 of each 7X5 matrix of letters.
var lgeoa="00100001000001000001100011000101110";//7X5 matrix letter A
var lgeob="10000111000010001010100011000101110";//7X5 matrix letter B
and so on...
Drawing letter means change corresponding div background color. It is working fine, but after that I wanted to animate them the problem started. I clear line and draw in every 10 milliseconds, but its very very laggy. So please how can this code be optimized to work without lags?
P.S. Surprisingly it's working better in IE11 rather than in chrome.
Here is a fiddle
There's a lot of optimization that can be done here. I'll point out a couple.
Starting with the animate function, the first thing I notice is that you're running a bit of code every 10ms. Why don't we check out what's being run?
function animate() {
var string = "აბგდევზთიკლმნოპჟრსტუფქღყშჩცძწჭხჯჰ ტესტ ტესტ აი ემ ე თეიბლ ტექსტი იწერება აქ"; //string to animate
position = 150; //initial position of string
window.setInterval(function () {
clearLine(0);
drawOnBoard(string, position, 0);
position = position - 1;
}, 10);
}
Clearline is the first function.
function clearLine(n){
for(var i=n*symbolHeight*lineWidth+n*lineWidth;i<(n+1)*symbolHeight*lineWidth+n*lineWidth;i++)
leds[i].style.backgroundColor="black";
}
That's a bit of a mess in the for loop. My understanding is that non-compiled code will run all of that math for every single iteration. So let's move it out of the for loop.
function clearLine(n) {
var initial = n * symbolHeight * lineWidth + n * lineWidth;
var length = (n + 1) * symbolHeight * lineWidth + n * lineWidth;
for (var i = initial; i < length; i++)
leds[i].style.backgroundColor = "black";
}
Ah but there's still more to be done. I see that both equations have a lot of shared math.
function clearLine(n) {
var whateverThisIs = symbolHeight * lineWidth + n * lineWidth;
var initial = n * whateverThisIs;
var length = (n + 1) * whateverThisIs;
for (var i = initial; i < length; i++)
leds[i].style.backgroundColor = "black";
}
I saw that you're moving on so I'll stop working on this for now. There's still plenty more to optimize.
Here's a fiddle of the updated version.

HTML 5 canvas animation - objects blinking

I am learning ways of manipulating HTML 5 Canvas, and decided to write a simple game, scroller arcade, for better comprehension. It is still at very beginning of development, and rendering a background (a moving star field), I encountered little, yet annoying issue - some of the stars are blinking, while moving. Here's the code I used:
var c = document.getElementById('canv');
var width = c.width;
var height = c.height;
var ctx = c.getContext('2d');//context
var bgObjx = new Array;
var bgObjy = new Array;
var bgspeed = new Array;
function init(){
for (var i = 1; i < 50; i++){
bgObjx.push(Math.floor(Math.random()*height));
bgObjy.push(Math.floor(Math.random()*width));
bgspeed.push(Math.floor(Math.random()*4)+1);
}
setInterval('draw_bg();',50);
}
function draw_bg(){
var distance; //distace to star is displayed by color
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(0,0,width,height);
for (var i = 0; i < bgObjx.length; i++){
distance = Math.random() * 240;
if (distance < 100) distance = 100;//Don't let it be too dark
ctx.fillStyle = "rgb("+distance+","+distance+","+distance+")";
ctx.fillRect(bgObjx[i], bgObjy[i],1,1);
bgObjx[i] -=bgspeed[i];
if (bgObjx[i] < 0){//if star has passed the border of screen, redraw it as new
bgObjx[i] += width;
bgObjy[i] = Math.floor(Math.random() * height);
bgspeed[i] = Math.floor (Math.random() * 4) + 1;
}
}
}
As you can see, there are 3 arrays, one for stars (objects) x coordinate, one for y, and one for speed variable. Color of a star changes every frame, to make it flicker. I suspected that color change is the issue, and binded object's color to speed:
for (var i = 0; i < bgObjx.length; i++){
distance = bgspeed[i]*30;
Actually, that solved the issue, but I still don't get how. Would any graphics rendering guru bother to explain this, please?
Thank you in advance.
P.S. Just in case: yes, I've drawn some solutions from existing Canvas game, including the color bind to speed. I just want to figure out the reason behind it.
In this case, the 'Blinking' of the stars is caused by a logic error in determining the stars' distance (color) value.
distance = Math.random() * 240; // This is not guaranteed to return an integer
distance = (Math.random() * 240)>>0; // This rounds down the result to nearest integer
Double buffering is usually unnecessary for canvas, as browsers will not display the drawn canvas until the drawing functions have all been completed.
Used to see a similar effect when programming direct2d games. Found a double-buffer would fix the flickering.
Not sure how you would accomplish a double(or triple?)-buffer with the canvas tag, but thats the first thing I would look into.

Categories