Delay function until an asset has loaded? - javascript

I am playing with the canvas, and it seems to be working great in FF6, but in Chrome 13, the sprite that I am drawing does not appear reliably. I have done some research and found that the problem stems from the function firing before the asset has loaded completely.
Fiddle here:
http://jsfiddle.net/LqHY9/
Relevant Javascript:
function sprite(ipath, sh, sw, ih, iw){
/* BASIC INFO FOR SPRITE */
this.frameWidth = sw;
this.frameHeight= sh;
frame_rows = ih/sh;
frame_columns = iw/sw;
num_frames = frame_columns*frame_rows ;
this.frame = new Array();
frameNumber = 0;
for(row = 0; row<frame_rows; row++){
for(i=0;i<frame_columns;i++){
this.frame[frameNumber] = {};
this.frame[frameNumber].offsetX = this.frameWidth*i;
this.frame[frameNumber].offsetY = this.frameHeight*row;
frameNumber++
}
}
this.sheight = sh;
this.swidth = sw;
this.raw = new Image();
this.raw.src = ipath;
}
animation=new sprite("http://www.melonjs.org/tutorial/tutorial_final/data/sprite/gripe_run_right.png",64,64,64,512);
context.drawImage(animation.raw, animation.frame[0].offsetX, animation.frame[0].offsetY, animation.frameWidth, animation.frameHeight, 0, 0, animation.frameWidth,animation.frameHeight)
(Don't worry, my context variable is defined, I just cut that bit out, you can see the whole thing in the JSFiddle.)

The Image object has an onload event which you should hook into.
Assuming you have more than one image, you could implement a sort of a "loader". This would basically just take an array of image URLs, load each of them, and listen to their onload events. Once each image has loaded, it would in turn call some other function, which would signal that every resource has finished loading.

the Image() object has an onload(and onerror) event. If you need to execute something after it loads you can attach a function.
e.g.
var img = new Image();
img.onload = function() {
//do something
};
Just make sure you attach the onload before setting the src.

You need to use the onload handler for the image. You must set the handler before you set the .src for the object because in some browsers, the load event may fire immediately upon setting .src if the image is in the browser cache. Here's a piece of pseudo code:
var img = new Image();
img.onload = function () {
// image is now loaded and ready for handling
// you can safely start your sprite animation
}
img.src = "xxx";
You can see related sample code from another answer I wrote here: jQuery: How to check when all images in an array are loaded?.

function Sprite(urls, speed, box)
{
var that = this, running = false, interval = 0, loaded = false;
that.urls = urls;
that.speed = speed;
that.images = new Array();
that.box = box || { x: 0.0, y: 0.0, w: 64, h: 64 };
for(var i = 0; i < that.urls.length; ++i)
{
that.images[i] = new Image();
that.images[i].src = that.urls[i];
that.images[i].id = i;
var len = that.urls.length;
that.images[i].onload = function(){ if(parseInt(this.id) === len) { loaded = true; } };
}
that.current = 0;
var Draw = function(ctx)
{
if(loaded)
{
var curr = that.images[that.current];
ctx.drawImage(curr, 0.0, 0.0, curr.width, curr.height, that.box.x, that.box.y, that.box.w, that.box.h);
}
};
that.Run = function(ctx)
{
if(!running)
{
running = true;
interval = setInterval(function(){
Draw(ctx);
if(that.current < that.urls.length)
{
that.current++;
}
else
{
that.current = 0;
}
}, that.speed);
}
};
that.Clear = function()
{
if(running)
{
running = false;
clearInterval(interval);
}
};
}
// Exemple
var test = new Sprite(["image1.png", "image2.png", "image3.png"], 250);
test.Run(myContext);

Related

Canvas Clearing After Multiple DrawImage

I have been trying to draw a tile-like map to the canvas, although if i try to draw the same sprite twice, it will only render the final call of drawImage. Here is my code if it helps :
window.onload = function(){
function get(id){
return document.getElementById(id);
}
var canvas = get("canvas");
ctx = canvas.getContext("2d");
canvas.width = 160;
canvas.height = 160;
grass = new Image();
water = new Image();
grass.src = "res/Grass.png";
water.src = "res/Water.png";
path = {
draw: function(image,X,Y){
X = (X*32)-32;
Y = (Y*32)-32;
image.onload = function(){
ctx.drawImage(image,X,Y);
}
},
}
path.draw(water,2,2);
path.draw(grass,1,2);
path.draw(grass,1,1);
path.draw(water,2,1);
}
You're overwriting onload on the image next time you call it.
Think of it this way:
var image = {
onload: null
};
// Wait for the image to "load"
// Remember, onload is asynchronous so it won't be called until the rest of
// your code is done and the image is loaded
setTimeout(function() {
image.onload();
}, 500);
image.onload = function() {
console.log('I will never be called');
};
image.onload = function() {
console.log('I overwrite the previous one');
};
Instead, you might try waiting for your images to load then do the rest of your logic.
var numOfImagesLoading = 0;
var firstImage = new Image();
var secondImage = new Image();
// This is called when an image finishes loading
function onLoad() {
numOfImagesLoading -= 1;
if (numOfImagesLoading === 0) {
doStuff();
}
}
function doStuff() {
// This is where you can use your images
document.body.appendChild(firstImage);
document.body.appendChild(secondImage);
}
firstImage.src = 'http://placehold.it/100x100';
firstImage.onload = onLoad;
numOfImagesLoading += 1;
secondImage.src = 'http://placehold.it/200x200';
secondImage.onload = onLoad;
numOfImagesLoading += 1;

draw image from canvas constructor prototype

I'm trying to preload a set of images and draw first one from the list on to multiple canvases using canvas constructor.
It seems I can't even start loading images using prototype function. Console log says this.setImages is not a function. I'm just starting with js therefore it might be a very simple mistake.
I left some comments in the code i hope it will make it a bit clear.
There're other ways to achieve what I'm asking but in my case i have to use constructor function.
var sources = [
'http://i.telegraph.co.uk/multimedia/archive/02830/cat_2830677b.jpg',
'http://r.ddmcdn.com/w_622/u_0/gif/05-kitten-cuteness-1.jpg',
'http://simplycatbreeds.org/images/Kitten.jpg'];
var images = [];
var canvas1 = new canvas('canvas1');
var canvas2 = new canvas('canvas2');
// load images using callback function
setImages(sources, function(images){
window.alert("Done loading... Start!");
this.draw();
});
// canvases constructor
function canvas(canvasId) {
this.canvas = document.getElementById(canvasId);
this.context = this.canvas.getContext('2d');
this.canvas.style.width = '100%';
this.canvas.style.height = '100%';
this.canvasId = canvasId;
this.setImages();
};
// preloading images
canvas.prototype.setImages = function(sources, callback){
var cnt = 0;
function imagesLoaded(){
cnt++;
if (cnt >= sources.length){
// passing the objet containing images to load as parameteres
callback(images);
}
}
for (var i=0; i<sources.length; i++) {
images[i] = new Image();
images[i].src = sources[i];
images[i].onload = function(){
imagesLoaded();
}
}
};
canvas.prototype.draw = function() {
// some code to manipulate image to be added here...
this.context.drawImage(images[1], 0, 0);
};
here is FIDDLE example
There are some things I have fixed.
First, I have moved the code that creates the two objects to the end of the JS. The reason is that the prototype functions must be loaded before you try to access them. I recommend using a event listener such as window.onload to prevent the code from starting before everything is loaded.
Second, I changed the setImage so it is called on the canvas objects.
var sources = [...];
var images = [];
// load images using callback function
// canvases constructor
function canvas(canvasId) {
this.canvas = document.getElementById(canvasId);
this.context = this.canvas.getContext('2d');
this.canvas.style.width = '100%';
this.canvas.style.height = '100%';
this.canvasId = canvasId;
//I removed this since it does not have the right params
//this.setImages();
};
// preloading images
canvas.prototype.setImages = function(sources, callback){
var cnt = 0;
var obj = this;
function imagesLoaded(){
cnt++;
if (cnt >= sources.length){
// passing the objet containing images to load as parameteres
callback(images, obj);
}
}
for (var i=0; i<sources.length; i++) {
images[i] = new Image();
images[i].src = sources[i];
images[i].onload = function(){
imagesLoaded();
}
}
};
canvas.prototype.draw = function() {
// some code to manipulate image to be added here...
this.context.drawImage(images[1], 0, 0);
};
var canvas1 = new canvas('canvas1');
canvas1.setImages(sources, function(images){
window.alert("Done loading... Start!");
this.draw();
});
var canvas2 = new canvas('canvas2');
canvas2.setImages(sources, function(images, obj){
window.alert("Done loading... Start!");
obj.draw();
});
Demo
Try moving setImages function inside canvas function like this:
function canvas(canvasId)
{
this.setImages = function(...){ };
...
this.setImages();
}
In line 13 you should call setImages on a canvas object: canvas1.setImages();

javascript drawImage() issues

Currently having some issues with drawImage();. Namely it wont actually draw. I tried it out with fillRect(); and it worked aswell as putting the drawImage(); inside the the onload function aswell (which worked).
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 640;
canvas.height = 400;
document.body.appendChild(canvas);
var tileArray = [
[0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,1,1,1,1,0,0,1,0,0,0],
[0,0,0,1,1,1,1,0,1,1,1,0,0],
[0,0,0,1,1,1,0,0,0,0,1,1,0],
[0,0,0,1,1,1,0,0,0,0,0,0,0],
[0,0,0,1,1,1,0,0,0,0,0,0,0],
[0,0,0,0,1,1,1,0,0,0,0,0,0],
[0,0,0,0,1,1,1,0,0,0,0,0,0],
[0,0,0,0,0,1,1,1,0,0,0,0,0],
[0,0,0,0,0,0,1,1,0,0,0,0,0]
];
var grassReady = false;
var grass = new Image();
grass.onload = function() {
grassReady = true;
};
grass.src = "images/grass.png";
var sandReady = false;
var sand = new Image();
sand.onload = function() {
sandReady = true;
};
sand.src = "images/sand.png";
var posX = 0;
var posY = 0;
if(grassReady) {
ctx.drawImage(grass, posX, posY);
}
Any pointers as to why this is would be greatly appreciated and I appologize in advance if messed up the code section in anyway. I went through other similar posts and coulden't find a solution that seemed to work.
As #Suman Bogati correctly says, you must wait for your images to load before using them in drawImage.
A Demo: http://jsfiddle.net/m1erickson/jGPGj/
Here's an image loader that preloads all images and then calls the start() function where you can use drawImage because all the images are fully loaded.
var imageURLs=[]; // put the paths to your images here
var imagesOK=0;
var imgs=[];
imageURLs.push("images/grass.png");
imageURLs.push("images/sand.png");
loadAllImages(start);
function loadAllImages(callback){
for (var i=0; i<imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function(){
imagesOK++;
if (imagesOK>=imageURLs.length ) {
callback();
}
};
img.onerror=function(){alert("image load failed");}
img.crossOrigin="anonymous";
img.src = imageURLs[i];
}
}
function start(){
// the imgs[] array holds fully loaded images
// the imgs[] are in the same order as imageURLs[]
// grass.png is in imgs[0]
// sand.png is in imgs[1]
}
This statement ctx.drawImage(); should be inside the grass.onload = function() {} function, something like
grass.onload = function() {
ctx.drawImage(grass, posX, posY);
}
If you define drawImage() outside the grass.onload() function, then that statment would executed first, so at that point grassReady is false, So the condition is not satisfied.
Bascially it's related to asynchronous concept.
Your code is running into order
1) First
var grassReady = false;
if(grassReady) {
//grassReady is false, this condition is not satisfied
ctx.drawImage(grass, posX, posY);
}
2) Second
grass.onload = function() {
grassReady = true;
};

AddEventListener and image loader trouble

I had problem with event listener and image loader in my code. When i try to load and check status of loaded image based on event listener some of images skipped. I think browser can handle only one event and when active one isn't stopped next can work and skip its work. So few of images return wront status and can be used. Just give me some advices about this. Here is my code:
//Image loader
var urlList = [
'images/ground_02.png', // 0 - FG
'images/ground_layer0.png', // 1 - CITY
...
];
var urlListLength = urlList.length; //length of URL-array
var iCount = new Number(0); // Create zero-counter
var images = new Array(); // All images array. Each elem stores one image
var imageInfo = function(imageName, imageCount, imageWidth, imageHeight) { // Constructor for image data container
this.imageName = imageName; // Url of image
this.imageCount = imageCount; // Frames
this.imageWidth = imageWidth; // Width of images
this.imageHeight = imageHeight; // Height of images
};
var imageData = new Array(); // Images data array
for (var i=0; i!=urlListLength; ++i) { // Loop for each image load
images[i] = new Image(); // Image array.
images[i].addEventListener('load', function(i) {
imageData.push(new imageInfo(images[iCount], images[iCount].width/images[iCount].height, images[iCount].width, images[iCount].height));
iCount++;
if (iCount == urlListLength) {
var loaded = true;
loop();
};
}, false);
images[i].src = urlList[i];
images[i].onerror=function() {
alert("FAIL "+this.src);
};
};
You have a problem at least here:
imageData.push(new imageInfo(images[iCount], images[iCount].width/images[iCount].height, images[iCount].width, images[iCount].height));
load event is fired once image is loaded by browser. But it is not necessary that it will be fired in the same order as load is started. Basically, images[2] could be loaded before images[0] and images[1] and in that case iCount won't work.
You can use this instead of images[iCount] as in this case this will point to exact that image which has fired load event.
imageData.push(new imageInfo(this, this.width/this.height, this.width, this.height));
I think browser can handle only one event and when active one isn't
stopped next can work and skip its work.
Nope. That is wrong. Regularly all events will be fired. But JS is single threaded, so it will simply put an event into a queue and will run next event after previous is done. It will not skip any event.
This should work for all browsers, including IE6.
var list = [
'images/ground_02.png',
'images/ground_layer0.png'
],
images = {};
function loadImage() {
images[this.src] = {
loaded: (this.width > 0 ? true : false),
src: this.src,
width: this.width,
height: this.height
};
if (list.length < 1) return;
var url = list.pop(),
img = new Image();
img.onload = loadImage;
img.onerror = loadImage;
img.src = url;
}
loadImage(); // Initialize loading
And if you need to check if an image has been loaded:
function isImageLoaded(src) {
return (images[src] && images[src].loaded);
}
if (isImageLoaded('images/ground_02.png')) alert('Yes!');

getImageData always returning 0

I have been trying to make a script that compares two images in HTML5 and Javascript. But for some odd reason, it always returns that the images are completely the same.
And when looking at what the problem could be, I found out that every data value of every pixel returned, for some odd reason, "0".
So, any idea of what I have done wrong? :)
For some reason I feel like it's something very simple, but I just learned about the canvas element, so yeah.
This is my code:
function compareImg() {
var c1 = document.getElementById("c");
var ctx1 = c1.getContext("2d");
var c2 = document.getElementById("c2");
var ctx2 = c2.getContext("2d");
var match = 0;
var img1 = new Image();
img1.src = "cat.jpg";
img1.onload = function() {
ctx1.drawImage(img1, 0, 0);
}
var img2 = new Image();
img2.src = "bird.jpg";
img2.onload = function() {
ctx2.drawImage(img2, 0, 0);
}
for(var x = 0; x<c1.width; x++) { // For each x value
for(var y = 0; y<c1.height; y++) { // For each y value
var data1 = ctx1.getImageData(x, y, 1, 1);
var data2 = ctx2.getImageData(x, y, 1, 1);
if (data1.data[0] == data2.data[0] && data1.data[1] == data2.data[1] && data1.data[2] == data2.data[2]) {
match++;
}
}
}
var pixels = c1.width*c1.height;
match = match/pixels*100;
document.getElementById("match").innerHTML = match + "%";
}
You are not waiting until your images have loaded and drawn before performing your comparison. Try this:
var img = new Image;
img.onload = function(){
ctx1.drawImage(img,0,0);
var img = new Image;
img.onload = function(){
ctx2.drawImage(img,0,0);
// diff them here
};
img.src = 'cat.jpg';
};
img.src = 'cat.jpg';
As shown above, you should always set your src after your onload.
I suspect that the problem is that your image data is probably not ready at the point you try to use it for the canvas. If you defer that code to the onload handlers, that will (probably) help:
var img1 = new Image(), count = 2;
img1.src = "cat.jpg";
img1.onload = function() {
ctx1.drawImage(img1, 0, 0);
checkReadiness();
}
var img2 = new Image();
img2.src = "bird.jpg";
img2.onload = function() {
ctx2.drawImage(img2, 0, 0);
checkReadiness();
}
function checkReadiness() {
if (--count !== 0) return;
for(var x = 0; x<c1.width; x++) { // For each x value
for(var y = 0; y<c1.height; y++) { // For each y value
var data1 = ctx1.getImageData(x, y, 1, 1);
var data2 = ctx2.getImageData(x, y, 1, 1);
if (data1.data[0] == data2.data[0] && data1.data[1] == data2.data[1] && data1.data[2] == data2.data[2]) {
match++;
}
}
}
var pixels = c1.width*c1.height;
match = match/pixels*100;
document.getElementById("match").innerHTML = match + "%";
}
All I did was add a function wrapper around your code. That function checks the image count variable I added, and only when it's zero (i.e., only after both images have loaded) will it do the work.
(This may be superstition, but I always assign the "onload" handler before I set the "src" attribute. I have this idea that, perhaps only in the past, browsers might fail to run the handler if the image is already in the cache.)
Now another thing: you probably should just get the image data once, and then iterate over the returned data. Calling "getImageData()" for every single pixel is going to be a lot of work for the browser to do.

Categories