I've been losing my mind over this. I spent 3 hours trying different methods and finding a solution online, and I still haven't fixed it.
I have two separate images(not a spritesheet) and they need to be displayed one after the other, as an animation, infinitely. Here's is my latest code:
var canvas, context, imageOne, imageTwo, animation;
function init(){
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
imageOne = new Image();
imageTwo = new Image();
imageOne.src = "catone.png";
imageTwo.src = "cattwo.png";
// Just to make sure both images are loaded
setTimeout(function() { requestAnimationFrame(main);}, 3000);
}
function main(){
animation = {
clearCanvas: function(){
context.clearRect(0, 0, canvas.width, canvas.height);
},
renderImageOne: function(){
context.drawImage(imageOne, 100, 100);
},
renderImageTwo: function(){
context.drawImage(imageTwo, 100, 100);
}
};
animation.renderImageOne();
// I also tried calling animation.clearCanvas();
context.clearRect(0, 0, canvas.width, canvas.height);
animation.renderImageTwo();
// I put this here to confirm that the browser has entered the function, and that it hasn't stopped after animation.renderImageTwo();
console.log("cats");
requestAnimationFrame(main);
}
init();
But the problem is that the only one image is displayed, and it's not moving. I can't see any errors or warnings in the console. I'm also sure HTML and JavaScript are connected properly and the images are in the right path. So in any case, only the image in the first function is displayed. Example: animation.renderImageOne(); displays catone, but if I replace it with animation.renderImageTwo(); it displays cattwo.
The problem is here:
animation.renderImageOne();
// I also tried calling animation.clearCanvas();
context.clearRect(0, 0, canvas.width, canvas.height);
animation.renderImageTwo();
Is it's drawing the first image, clearing the canvas, then drawing the second image, then after all that it draws to the screen. Leaving you with only seeing the second image. You will need a variable that alternates values, and use that to determine which picture you should draw:
var canvas, context, imageOne, imageTwo, animation;
var imageToDraw = "one";
And then:
function main() {
...
if(imageToDraw == "one") {
animation.renderImageOne();
imageToDraw = "two";
}
else if(imageToDraw == "two") {
animation.renderImageTwo();
imageToDraw = "one";
}
...
}
Note: You don't need to define animation inside main(), you can move it into global scope. That way you don't redefine it each time you call main().
Related
I want to render a video in a canvas with 25fps or more. So I use CanvasRenderingContext2D#drawImage() to render every frame in the canvas. It works in chrome69 and FireFox. But it does not work in chrome70.
Here is the code fragment:
if (frame >= this.inFrame && frame <= this.outFrame) {
// this.ctx.drawImage(this.video,
// this.sourceRect.x, this.sourceRect.y, this.sourceRect.width, this.sourceRect.height,
// this.rect.x, this.rect.y, this.rect.width, this.rect.height);
this.frame.init(this.canvas); // line 6, breakpoint here.
this.frame.isPlaying = this.isPlaying;
let image = this.frame;
for (let i = 0; i<this.filters.length;i++) {
image = this.filters[i].getImageWithFilter(frame, image);
}
return image;
}
I put a breakpoint at line 6. At this time, The video is loaded.
this.video.readyState=4
And I execute this command in dev tools.
document.getElementById("test_canvas").getContext('2d').drawImage(this.video, 0, 0);
Sometimes the test canvas shows the correct video frame, sometimes not, just nothing to show.
Let's keep the program going on, the canvas will show the correct video frame finally.
So I doubt that the CanvasRenderingContext2D#drawImage() is an async method in Chrome70. But I find nothing in Chrome website.
Could anyone help me with this question or help me render correctly in every frames.
You can check that by imply calling getImageData() or even just fillRect() right after your drawImage() call.
If the returned ImageData is empty, or if there is no rect drawn over your video frame, then, yes, it might be async, which would be a terrible bug that you should report right away.
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var vid = document.createElement('video');
vid.muted = true;
vid.crossOrigin = 'anonymous';
vid.src = 'https://upload.wikimedia.org/wikipedia/commons/transcoded/a/a4/BBH_gravitational_lensing_of_gw150914.webm/BBH_gravitational_lensing_of_gw150914.webm.480p.webm'
vid.play().then(() => {
ctx.drawImage(vid, 0, 0);
console.log(
"imageData empty",
ctx.getImageData(0, 0, canvas.width, canvas.height).data.some(d => !!d) === false
);
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, 50, 50);
});
document.body.append(canvas);
So now, to give a better explanation as to what you saw, the debugger will stop the whole event loop, and Chrome might have decided to not honor the next CSS frame drawing when the debugger has been called. Hence, what is painted on your screen is still the frame from when your script got called (when no image was drawn).
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var vid = document.createElement('video');
vid.muted = true;
vid.crossOrigin = 'anonymous';
vid.src = 'https://upload.wikimedia.org/wikipedia/commons/transcoded/a/a4/BBH_gravitational_lensing_of_gw150914.webm/BBH_gravitational_lensing_of_gw150914.webm.480p.webm'
vid.play().then(() => {
ctx.drawImage(vid, 0, 0);
const hasPixels = ctx.getImageData(0, 0, canvas.width, canvas.height).data.some(d => !!d);
alert('That should also block the Event loop and css painting. But drawImage already happened: ' + hasPixels)
});
document.body.append(canvas);
canvas{
border:1px solid;
}
I'm programming a game
I have the next settings:
HTML game.html
<canvas id="canvas" width="288" height="512"></canvas>
<script src="game.js"></script>
JAVASCRIPT game.js
var document;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//load images
var pipeNorth = new Image();
pipeNorth.src = "images/pipeNorth.png";
function draw(){
/*code */
alert(pipeNorth.height);
requestAnimationFrame(draw);
}
draw();
PROBLEM
If I set an alert displaying the image height, it shows 0 during the first two or three alerts, then it starts showing the real height.
Why does this happen? How do I fix this? I think it means the image is not fully loaded when the game begins?
From my experience this happens because the document rendering takes some time. My technique for this case is a time delay:
function draw(){
var delay = 200;//Your delay in milliseconds
setTimeout(function(){
/*code */
alert(pipeNorth.height);
requestAnimationFrame(draw);
},delay);
}
Is it usable for you?
Yes you are right the image is not fully loaded when the game begins.to fix this you simply have to use the "load" event :
var document;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//load images
var pipeNorth = new Image();
pipeNorth.src = "images/pipeNorth.png";
function draw(){
/*code */
alert(pipeNorth.height);
requestAnimationFrame(draw);
}
pipeNorth.addEventListener("load",function(){
//this function will execute when the image is fully loaded
draw();
});
i'm setting up my home web page where i want to have live feed from my 7 cameras. For 6 of them i must use a refreshing jpeg, since all i can get is a snapshot of the current view or an rtsp feed. For the last one i use an iframe since i can get a constant refreshing web page.
So, this is the script that i use to refresh the snapshot
<script>
var imageObj = new Image();
imageObj.onload = function() {
drawOnCanvas();
setTimeout(timedRefresh, 100);
}
// set src AFTER assigning load
imageObj.src = "http://192.168.2.136/snap.jpeg?" + Math.random();
function timedRefresh() {
imageObj.src = "http://192.168.2.136/snap.jpeg?" + Math.random();
//drawOnCanvas(); //flicker remover
}
function drawOnCanvas() {
var canvas = document.getElementById("canvas1");
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(imageObj, 0, 0, canvas.width, canvas.height);
}
</script>
It works smoothly but when i add a second canvas, creating a new script afther the previous where i change the id to "canvasX" and the target IP to another camera
<script>
var imageObj = new Image();
imageObj.onload = function() {
drawOnCanvas();
setTimeout(timedRefresh, 100);
}
// set src AFTER assigning load
imageObj.src = "http://192.168.2.122/snap.jpeg?" + Math.random();
function timedRefresh() {
imageObj.src = "http://192.168.2.122/snap.jpeg?" + Math.random();
//drawOnCanvas(); //flicker remover
}
function drawOnCanvas() {
var canvas = document.getElementById("canvas2");
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(imageObj, 0, 0, canvas.width, canvas.height);
}
</script>
and i try to render both canvas using this code in the body section
<canvas id="canvas1" width="1080" height="608"> </canvas>
<canvas id="canvas2" width="1080" height="608"> </canvas>
<canvas id="canvas3... etc
only the last canvas is rendered and all the others are simply not rendered.
All the feeds should be one on top of the other, so when i scroll (this is smartphone focused) i can see all the streams; this is why there is no spacing or else, due to the 1080x1920 of most of the home devices. I'm hosting on an apache server on a RaspberryPi 3.
I kinda solved it. I created 6 diffrent html pages with only the canvas script in it, then i added 6 iframe pointing to each html page. Maybe it's not clean but it works. This way i need 5MB/second, it only works on LAN.
I am learning html canvas and below is my html code.
<!DOCTYPE html>
<html>
<head>
<script language="javascript">
function moveImage(x) {
var context = document.getElementById('myCanvas').getContext("2d");
var img = new Image();
img.onload = function () {
context.drawImage(img, x, 259);
}
img.src = "flower.jpg";
}
function startDrawing() {
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.beginPath();
context.moveTo(50, 300);
context.lineTo(950, 300);
context.stroke();
var x=50;
setInterval(function() {
x = x+20;
moveImage(x);
}, 1000);
}
</script>
</head>
<body onload="startDrawing()">
<canvas id="myCanvas" width="1000" height="1000">
</body>
</html>
Please find the below output from this code:
How can I remove the traces of 'older frames' (of the flower), as you could see lots of flowers while it is moving from left to right in the screen shot ? Please help the code changes required.
Thanks.
The problem is you aren't clearing the canvas before drawing on it again. You can clear it using clearRect.
setInterval(function() {
// Clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height);
x += 20;
moveImage(x);
}, 1000);
Keep in mind that this will clear the entire canvas. If you're rendering anything else, you'll want to redraw it after the clear.
You need to re-render the frame. Here's some code from an incomplete game I wrote:
var main = function () {
var now = Date.now(),
delta = now - then;
update(delta / 1000);
render();
then = now;
// Request to do this again ASAP
requestAnimationFrame(main);
};
GitHub repo code
My render function contains the context.drawImg() work (getting properly re-rendered), and that's similar to your moveImage function.
Edit: A little explanation. The image traces are previous renderings of the your image at each updated position. Without the frame itself being reset, each move of the image is preserved on the screen, giving the appearance of a trail of images.
I am trying to draw a line on top of an image background - in an HTML5 Canvas .
However always the line gets drawn behind the image . Actually the line gets drawn first and then the pictures get drawn - irrespective of how I call the functions.
How do I bring the line to the top of the image ?
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
context.clearRect(0, 0, canvas.width, canvas.height);
drawbackground(canvas, context);
drawlines(canvas, context);
function drawbackground(canvas, context){
var imagePaper = new Image();
imagePaper.onload = function(){
context.drawImage(imagePaper,100, 20, 500,500);
};
imagePaper.src = "images/main_timerand3papers.png";
}
function drawlines(canvas, context){
context.beginPath();
context.moveTo(188, 130);
context.bezierCurveTo(140, 10, 388, 10, 388, 170);
context.lineWidth = 10;
// line color
context.strokeStyle = "black";
context.stroke();
}
Totally untested code, but did you tried something like this?
function drawbackground(canvas, context, onload){
var imagePaper = new Image();
imagePaper.onload = function(){
context.drawImage(imagePaper,100, 20, 500,500);
onload(canvas, context);
};
imagePaper.src = "images/main_timerand3papers.png";
}
and then call the method like this...
drawbackground(canvas, context, drawlines);
Change your image onload to something like this:
imagePaper.onload = function () {
context.drawImage( imagePaper, 100, 20, 500, 500 );
drawLines( canvas, context );
};
Then make sure you remove the earlier call to drawLines.
The important take away to this solution, is that the onload function will be executed sometime in the future, whereas the drawLines function is executed immediately. You must always be careful of how you structure your callbacks, especially when nesting them.
To be more efficient, assuming you are going to be doing multiple redraws of this line or lines, would be to set the CSS background-image of the canvas to be your image.
<canvas style="background-image:url('images/main_timerand3papers.png');"></canvas>
Or you can try this:
drawbackground(canvas, context);
context.globalCompositeOperation = 'destination-atop';
drawlines(canvas, context);