I was playing around with pixel-level manipulations on an HTML canvas and ran into a bug with the putImageData() function. Here's my code:
var can = document.getElementById("canvasID");
var ctx = can.getContext("2d");
var picToAlter = document.getElementById("image");
var pix = ctx.createImageData(1, 1);
var colData = pix.data;
colData[someNumber] = someValue;
ctx.drawImage("image", 0, 0);
for(let i=0;i<=can.width; i+10){
ctx.putImageData(pix, i, 0);
}
When I try to run this code in my browser (Firefox) the page simply fails to load no matter how long I wait, and, eventually, asks me if I want to stop the loading process. The problem seems to lie with my for-loop, because if I run a single instance of the code, it works. The moment I put it in a loop, though, it goes back to not loading. This is true even if I use a while-loop instead. I know that putImageData can be slow, but I feel like I'm missing something obvious that makes the loop infinite, but I just can't find it.
If you are trying to grab image data immediately (without preloading), you probably see a blank canvas. You should only run your code after preloading the image.
picToAlter.addEventListener('load', yourDrawingMethod)
Also, your for-loop is probably not behaving how you would expect. You don't want to hit can.width because it would actually be out of bounds because it's starting at zero. Also, your iteration statement isn't actually iterating. You need to use an assignment operator like i = i+10 or i+=10. Taking those issues into consideration, you would end up with something like this...
for(let i = 0; i < can.width; i+=10){
// ...
}
Related
I'm a beginner on here, so apologies in advance for naivety. I've made a simple image on Brackets using Javascript, trying to generate circles with random x and y values, and random colours. There are no issues showing when I open the browser console in Developer Tools, and when I save and refresh, it works. But I was expecting the refresh to happen on a loop through the draw function. Any clues as to where I've gone wrong?
Thanks so much
var r_x
var r_y
var r_width
var r_height
var x
var y
var z
function setup()
{
r_x = random()*500;
r_y = random()*500;
r_width = random()*200;
r_height = r_width;
x = random(1,255);
y= random(1,255);
z= random(1,255);
createCanvas(512,512);
background(255);
}
function draw()
{
ellipse(r_x, r_y, r_width, r_height);
fill(x, y, z);
}
Brackets.io is just your text editor (or IDE if you want to be technical) - so we can remove that from the equation. The next thing that baffles me is that something has to explicitly call your draw() method as well as the setup() method -
I'm thinking that you're working in some sort of library created to simplify working with the Canvas API because in the setup() method you're calling createCanvas(xcord,ycord) and that doesn't exist on it's own. If you want to rabbit hole on that task check out this medium article, it walks you thru all the requirements for creating a canvas element and then drawing on that canvas
Your also confirming that you're drawing at least 1 circle on browser refresh so i think all you need to focus on is 1)initiating your code on load and 2)a loop, and we'll just accept there is magic running in the background that will handle everything else.
At the bottom of the file you're working in add this:
// when the page loads call drawCircles(),
// i changed the name to be more descriptive and i'm passing in the number of circles i want to draw,
// the Boolean pertains to event bubbling
window.addEventListener("load", drawCircles(73), false);
In your drawCircles() method you're going to need to add the loop:
// im using a basic for loop that requires 3 things:
// initialization, condition, evaluation
// also adding a parameter that will let you determine how many circles you want to draw
function drawCircles(numCircles) {
for (let i = 0; i < numCircles; i++) {
ellipse(r_x, r_y, r_width, r_height);
fill(x, y, z);
}
}
here's a link to a codepen that i was tinkering with a while back that does a lot of the same things you are
I hope that helps - good luck on your new learning venture, it's well worth the climb!
Thank you so much for your help! What you say makes sense - I basically deleted the equivalent amount of code from a little training exercise downloaded through coursera, thinking that I could then essentially use it as an empty sandpit to play in. But there's clearly far more going on under the hood!
Thanks again!
I have a question over at collision-detection about a similar issue, but it's not exactly the same. I had an issue with a new game project (I'm trying to learn more about HTML5 Canvases and Socket.io) in which my collisions weren't working. I thought that my issue was centered on collisions, but now I'm starting to think something different. The reason I have a different issue posted here at the for-loop area is because I'm not sure if my issue is for-loop related or collision-detection related. Either way, I'd be happy to take one of my questions down.
This code is looping every frame to get the active positions of bullets and ships. If the bullet touches the ship, it'll be removed and some health points will be removed from the ship.
Tutorial I was using: http://jlongster.com/Making-Sprite-based-Games-with-Canvas
That aside, here's my checkCollisions code. It seems that the collision function is working, because when I started to log all the positions every time we had an iteration, it seemed that the position of my object was changing every single time. Is this one of those for-loop issues where I'm going to need a callback?
Thank you so much in advance for all your help. I'll be sure to upvote/select every response that helps out! :)
SOLVED! Turns out one of my arrays wasn't being passed in correctly. I'd like to thank you guys for telling me to always split it into multiple functions, that really helped me figure that one out!
// Let's start out here: I have a players[] array
//that's essentially a list of all players on the server
// and their positions. I omitted server connection functionality since that's not my error
// source.
function checkCollisions() {
for (var i = 0; i < players.length; i++) { // Iterating through all players
var pos = [players[i].posX, players[i].posY];
var size = [SHIP_WIDTH, SHIP_HEIGHT]; // This is the size of each player, it's a ship game. So these are constants.
if (players[i].userId != PLAYER.userId) { // Each player has a userId object, this is just doublechecking if we're not uselessly iterating
for (var j = 0; j < bullets.length; j++) { // We're now looping through bullets, an array of all the bullets being shot by players
var pos2 = bullets[j].pos;
var size2 = BULLET_SIZE;
var sender = bullets[j].sender;
if (boxCollides(pos, size, pos2, size2)) { // Collision code
if (sender != players[i].userId) {
bullets.splice(j, 1);
i--; // Tried here with j--, and by removing the entire line. Unfortunately it doesn't work :(
break;
}
}
}
}
}
}
Have you tried using console.log to see where the program is breaking? This might help you determine if there are multiple bugs or if it's just this one. If there's something wrong in a previous statement, you may not know it if you've fixed the i-- / j-- ...?
Edit: AH I see that you've fixed things, after I'd posted this. Well congrats and good job!
My Question
The goal is to make sure all images are fully loaded before a new game can begin. My second solution (Fiddle B) achieves this goal more consistently and accurately than my first solution (Fiddle A). Why?
JSFIDDLE A
JSFIDDLE B
Methods
Here is what both of my fiddle solutions do to preload images:
There is an array of absolute image URLS for all of the assets required by the game
The init() function has a for loop which generates a new canvas Image() per URL in the array
Each newly created image is pushed into another array
So, we now have the first array containing URL strings, and a second array containing 'HTMLImageElement' objects
Fiddle B differs from Fiddle A, in that it utilises the '.onload' event, plus a counter. The two fiddles use different ways of checking to see if all the image assets have loaded:
Fiddle A: compares the length of the two arrays. If they match, start the game
for (var i = 0; i < allGameImageUrls.length; i++) {
var img = new Image();
img.src = allGameImageUrls[i];
allGameImages.push(img);
console.log(allGameImages.length);
if (allGameImages.length >= allGameImageUrls.length) {
setUpGame();
} else {
// images haven't loaded yet
console.log('STILL LOADING');
STAGE.fillText("Loading ...", 20, 400);
}
}
Fiddle B: compares the second array length with the counter variable. This counter goes up by 1 every time an image's '.onload' event completes.
for (var i = 0; i < allGameImageUrls.length; i++) {
var img = new Image();
img.onload = function () {
assetCount++;
console.log('assetCount = ' + assetCount);
setUpGame();
};
img.src = allGameImageUrls[i];
allGameImages.push(img);
}
My Question Expanded
Fiddle A frequently (but not always) triggers the start of a new game before the full list of image assets has been properly loaded, causing game errors. Fiddle B consistently loads all of the image assets before allowing a new game to start. This can be seen in both fiddles from the 'Fully loaded' messages written to the canvas.
Although I can see that Fiddle A works better than Fiddle B, I don't understand why it is superior. Not all tutorials relating to loading HTMLImageElements use '.onload', and the tutorials that don't use it seem perfectly adequate.
Additionally, I don't understand why comparing the lengths of two arrays is not as accurate as comparing the second array length to a counter.
I understand how the two solutions differ, but I want to know why solution B works better than solution A.
Previous Research
Here are just a few examples of previous research I have done in order to try to answer my own question.
An article on pre-loading images which doesn't have any reference to an .onload event
The accepted solution to this question does not use .onload event, but it still works
The accepted solution to this other question is very similar to my Fiddle B (although I discovered it much later). However, the explanation of the solution hasn't helped me to answer my own question.
You are comparing two very different approaches: sequential with for (which is incorrect) and event-based.
Image downloading is asynchronous process so when the image src property is set the browser starts downloading it. It can be, however, very fast especially if the image was already had been downloaded by the browser and cached internally (in fact, it is blazingly fast). So when the next iteration starts it is already available (or, at least, almost all of them are available at the end of the loop). But if you clear the cache or use incognito mode and download them from the remote location (not your local server) then boom! - the loop ends with no image downloaded at all.
Another approach slightly better but the game is set up for every image downloaded, which is probably do not what is required.
Consider the following approach:
var length = allGameImageUrls.length,
count = 0;
var i, img;
for (i = 0; i < length; i++) {
img = new Image();
img.onload = function () {
count++;
// count is increased on every callback
// so if number of executed callbacks equals
// the number of images then all the images
// are downloaded
if (count === length) {
setUpGame();
}
};
img.src = allGameImageUrls[i];
allGameImages.push(img);
}
The only drawback is if one of the images does not exist, the game never starts so you need to workaround it with timeout.
I am trying to create an animation using a sprite sheet and a for loop to manipulate the background position until it has reached the total number or rows in the sheet. Ideally a reset back to the initial position would be practical, but I cannot even get the animation itself to trigger...
With the current function, no errors occur and the background position in my CSS does not change. I even recorded using Chrome DevTools Timeline and there was nothing either then everything related to my page loading. I have also tried using "background-position-y" as well as a simpler value rather then the math I currently have in place.
This is my function:
$(document).load(function() {
var $height= 324;
var $rows= 34;
for(var i=0; i<$rows; i++){
setTimeout(function() {
$('#selector').css("background-position", "0px ", "0" - ($height*i) + "px");
}, 10);
}
});
I hate to ask a question that is similar to previous issues, but I cannot seem to find another individual attempting sprite sheet animation with a for loop, so I suppose it is it's own problem.
p.s. I didn't include a snippet of my HTML and CSS because it is pretty standard and I don't see how that could be the problem. That being said, I am all ears to any potential thoughts!
I am completely revamping my answer
This issue is that the for() loop is not affected by the setTimeout so the function needs to be written on our own terms, not with a loop
Working Fiddle
Here it is..
var $height= 5;
var $rows= 25;
var i = 1; // Starting Point
(function animateMe(i){
if(i<=$rows){ // Test if var i is less than or equal to number of rows
var newHeight = 0-($height*i)+"px"; // Creat New Height Position
console.log(i); //Testing Purposes - You can Delete
$('#selector').css({"background-position": "0px "+ newHeight}); // Set New Position
i++; // Increment by 1 (For Loop Replacement)
setTimeout(function(){animateMe(i)}, 1000); // Wait 1 Second then Trigger Function
};
})(0);
Here is your solution
First Change
$(document).load() To $(document).ready()
And Change .css Syntex as
$('#selector').css("background-position",'0px '+(0 - ($height*i))+'px');
Here is fiddle Check it ihad implemented it on my recent project http://jsfiddle.net/krunalp1993/7HSFH/
Hope it helps you :)
I am trying to visualise a reinforcement agent moving through a 2d grid. I coded up a visualisation using canvas, and everytime my agent makes a move I try to update the grid. I was hoping to see an animation, but instead I see nothing until the agent has completed all this moves and I see the final state. If I step through with Google Chromes Developer tools then I can see the individual steps. I do not think it is a problem of my code just running to fast, because each step takes a couple of seconds.
My implementation is as follows, with the function gridWorld() called once to create a new object and executeAction called every time I want to draw. As shown I have used ctx.save(), and ctx.restore(), but that is only an attempt to solve this problem, and it seems to have made no difference.
Thanks
var execute gridWorld = function(action) {
var canvas = document.getElementById("grid");
this.ctx = canvas.getContext("2d");
this.executeAction = function(action) {
this.ctx.save()
// ... Do reinforcement learning stuff
// For every cell in grid, do:
this.ctx.fillStyle = "rgb(244,0,0)"
this.ctx.fillRect(positionX, poisitonY, 10,10)
this.ctx.restore();
}
}
Even if the code takes a long time to execute, the browser will not update the display until there is an actual break in the code. Use setTimeout() to cause a break in code execution whenever you want the canvas to update.
Your not going to see animations because they are happening way too fast. You need to break them up like in the following example.
Live Demo
If I did something like this for example
for(x = 0; x < 256; x++){
player.x = x;
ctx.fillStyle = "#000";
ctx.fillRect(0,0,256,256);
ctx.fillStyle = "#fff";
ctx.fillRect(player.x,player.y,4,4);
}
You would only ever see the player at the end of the board every time that function is called, and you wouldn't see any of the animations in between, because the loop runs too fast. Thats why in my live demo I do it in small increments and call the draw every 15 milliseconds so you have a chance to actually see whats being put on the canvas.