Javascript (canvas) - for loops and drawImage not working together [duplicate] - javascript

This question already has an answer here:
CanvasContext2D drawImage() issue [onload and CORS]
(1 answer)
Closed 7 years ago.
I'm trying to load a bunch of images onto a canvas, but non of them are appearing. The sources contain the links to the images I want to use. Anyone have any ideas?
This is my first time working with canvas.
<canvas id ="canvas" width = "500" height = "500"></canvas>
<script>
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
loadImages(ctx);
function loadImages()
{
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var number = 0;
var bX = 0;
var bY = 0;
var images = [];
images = getImages();
for(var i = 0;i<images.length;i++){
var t = images[i];
document.write(t.src+"<br>");
ctx.drawImage(t,0,0,100,100);
if(i%4==0)
{
bX = 0;
bY -= 110;
}
else
{
bX+=110;
}
}
}
I did this function to preload the images and return them in an array
function getImages()
{
var imgList = [];
var sources =
[ "http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_00.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_01.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_02.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_03.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_04.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_05.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_06.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_07.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_08.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_09.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_10.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_11.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_12.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_13.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_14.png" ];
var s = 0;
var length = sources.length;
for(s; s< length;++s)
{
imgList[s] = new Image();
imgList[s].src = sources[s];
}
return imgList;
}
</script>
</body>
</html>

First of all, make sure that the images are retrieved and loaded properly. Then adjust your code to something like below:
for (var i = 1; i <= images.length; i++) {
var t = images[i];
document.write(t.src+"<br>");
ctx.drawImage(t,bX,bY,100,100);
if (i%4 === 0) {
bX = 0;
bY += 110;
}
else {
bX += 110;
}
}
You want to iterate from index 1 instead of 0, so that the if statement i % 4 === 0 is not fulfilled right off the bat. Then you want to use the variables bX and bY as the position offsets from the images. You used ctx.drawImage(t,0,0,100,100); before which stacked all the images in the same position. And finally, increment bY in order to push the images down.

The problem is that the images arent loading before you try to draw them. You should create the imgs and append them to the body
images = getImages();
for(var i = 0; i<images.length; i++){
var img = document.createElement("img");
img.src= images[i].src;
document.body.appendChild(img);
img.style.display = "none";
img.id="img"+String(i);
}
then draw them like this
loadImages(images);
function loadImages(images){
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
for(var i = 0;i<images.length;i++){
ctx.drawImage(document.getElementById("img"+String(i)),i*10,0,100,100);
if(i%4==0)
{
bX = 0;
bY -= 110;
}
else
{
bX+=110;
}
}
}
I tested it out and it worked

It is not working, among other reasons, because you are not waiting for the images to load before you try to use them. You can do this using the Image object's load event. I would have written it something like this, using Promises to manage the loading state.
var images,
nameList = document.getElementById('image-names'), // just a div to display the stuff you were writing out to document. write.
// this function returns a Promise which will resolve once all of the
// images are done loading
preloadImages = function () {
var imagePromises,
sources = [
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_00.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_01.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_02.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_03.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_04.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_05.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_06.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_07.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_08.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_09.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_10.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_11.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_12.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_13.png",
"http://terminus.scu.edu/~ntran/csci168-f15/hw/hw3/tile_14.png"
];
// if we have already loaded and cached the images,
// return them right away wrapped in a resolved Promise
if (images) {
nameList.innerHTML += 'Using cached images.<br>';
return Promise.resolve(images);
}
// otherwise we use .map to iterate over the items in sources and
// create a new array of promises and store them in imagePromises
imagePromises = sources.map(function (src) {
// each of the promises that are created by this function
// are stored in imagePromises and will resolve when the image
// it represents fires its load event
return new Promise(function (resolve, reject) {
var img = new Image();
// once the image has loaded, resolve its Promise
img.onload = function () {
resolve(img);
};
// if there is an error reject this image's Promise
img.onerror = function () {
reject(src + ' failed to load.');
};
img.src = src;
});
});
// Promise.all will create a Promise that will resolve when all of the
// images are loaded (because all of the Promises representing those
// images will have resolved). If there is an error loading any of the
// images it will be rejected, which we can handle later.
return Promise.all(imagePromises)
// after Promise.all returns a resolved Promise, we create a new Promise
// using .then, which is what actually gets returned by preloadImages.
.then(function (loadedImages) {
// cache the loaded images in case we need them later
images = loadedImages;
// return the images so that anything chained off of this Promise
// has access to them.
return loadedImages;
});
},
displayImages = function () {
var c = document.getElementById("canvas"),
ctx = c.getContext("2d"),
bX = 0,
bY = 0;
// preloadImages will return a Promise that will resolve once all of the
// images have been loaded. The .then chained off of that Promise receives
// the list of images via the images parameter
preloadImages().then(function (images) {
var i,
img;
for(i = 0; i < images.length; /* no increment expression, see below */) {
img = images[i];
nameList.innerHTML += img.src + "<br>";
ctx.drawImage(img, bX, bY, 100, 100); // you need to make sure to
// use your offset here
// incrementing here instead of in the increment expression of
// for statement makes this part work correctly
i += 1;
if (i % 4 === 0) {
bX = 0;
bY += 110;
} else {
bX += 110;
}
}
}).catch(function (msg) {
// if there was an error loading any of the images, this .catch
// will display an error
nameList.innerHTML += msg + '<br>';
});
};
displayImages();
There is a working fiddle here. If you add a non-existent image to your sources list you will notice that it displays an error message instead of drawing to the canvas.
You don't have to use Promises, you could instead make preloadImages take a callback as an argument and then call that once all of the images are loaded. In that case you would have to manually keep track of how many images have been loaded and in the load event of each image check to see if all the images are loaded before calling the callback.
My coding style might look a bit strange to you since I make it a habit to use function expressions instead of function statements and a few other things like the strict equals operator (===). I would recommend Douglas Crockford's book JavaScript: The Good Parts if you would like to know more about this style of coding and why I use it. (It is a great book and I recommend it even if you don't.)

Related

Drawing an array of images on canvas in javascript [duplicate]

So I have been recently developing a site, The problem is the backgrounds for each page are images, and as a result, on slower connections (which is the case of some of the target audience) the images load progressivly as they are downloaded, to resolve this I am trying to make a preloading page that does the following :
Loads the Images
Once the loading is done, redirects the user to the requested page
<script type="text/javascript">
<!--//--><![CDATA[//><!--
var images = new Array()
var count=0;
function preload() {
for (i = 0; i < preload.arguments.length; i++) {
images[i] = new Image()
images[i].src = preload.arguments[i]
}
if(count==4) {
window.location = "index.html";
}
}
preload(
"backgrounds/bg1.jpg",
"backgrounds/bg2.jpg",
"backgrounds/bg3.jpg",
"backgrounds/bg4.jpg"
)
//--><!]]>
The problem is it redirects directly (I assume that it just starts the download of the image then directly adds one to the counter variable, quickly reaching 4 and not giving the image the time to download.
Any ideas how I can either make it signal me when the images have finished downloading, or only execute the redirect after it has done downloading the images ?
You need to wait for the load event. It's quite simple:
function preload(images, timeout, cb) {
var cbFired = false,
remaining = images.length,
timer = null;
function imageDone() {
remaining--;
if(remaining === 0 && !cbFired) {
cbFired = true;
clearTimeout(timer);
cb();
}
}
function timerExpired() {
if(cbFired)
return;
cbFired = true;
cb();
}
for(var i = 0; i < images.length; i++) {
var img = new Image();
img.onload = imageDone;
img.onerror = imageDone;
img.src = images[i];
}
timer = setTimeout(timerExpired, timeout);
}
You need to check a few things so that users don't get stuck:
You need to wait for both load and error so that the page doesn't get stuck if an image fails to load.
You should set a maximum timeout.
Also, in your code, i was a global variable (no var declaration).
Here's how to use it:
var images = [ "backgrounds/bg1.jpg",
"backgrounds/bg2.jpg",
"backgrounds/bg3.jpg",
"backgrounds/bg4.jpg"];
preload(images, 10000 /* 10s */, function () {
window.location = 'next_page';
});
Modify your preloader so that it binds to the "onload" event of the Image object and when all callbacks are fired it redirects (untested sample code below):
var images = new Array()
var count = 0;
function preload() {
var numImages = preload.arguments.length
for (i = 0; i < numImages; i++) {
images[i] = new Image();
images[i].onload = doneLoading; // See function below.
images[i].src = preload.arguments[i];
}
function doneLoading() {
if (++count >= numImages) {
window.location = "index.html";
}
}
}
preload(
"backgrounds/bg1.jpg",
"backgrounds/bg2.jpg",
"backgrounds/bg3.jpg",
"backgrounds/bg4.jpg"
)

how to use drawImage after load all images in array

I want to draw image array with drawImage after all the images are loaded.There is a render problem with drawImage(), tried to solve with setTimeout() but its not working all the time.
Here is my code;
while(FocusItem.length>0)
{
FocusItem.pop();
}
ftx=canvas.getContext('2d');
focusImageBackground = new Image();
focusImageBackground.src = "./images/odaklanma/odaklanmaBackground.jpg";
if(RandomSoru==15)
finishSoru=true;
if(finishSoru)
{
RandomSoru = Math.floor((Math.random() * 15)+1);
tempRandomSoru=RandomSoru;
}
if(RandomSoru==tempRandomSoru)
{
RandomSoru = Math.floor((Math.random() * 15)+1);
}
var soru = new Object();
soru["image"] = new Image();
soru.image.src = './images/odaklanma/level/'+RandomSoru+'/soru.png';
soru["x"] = 341;
soru["y"] = 140;
FocusItem.push(soru);
var dogru = new Object();
dogru["image"] = new Image();
dogru.image.src = './images/odaklanma/level/'+RandomSoru+'/dogru.png';
dogru["x"] = xDogru;
dogru["y"] = 280;
FocusItem.push(dogru);
var yanlis = new Object();
yanlis["image"] = new Image();
yanlis.image.src = './images/odaklanma/level/'+RandomSoru+'/yanlis.png';
yanlis["x"] = xYanlis1;
yanlis["y"] = 280;
FocusItem.push(yanlis);
var yanlis2 = new Object();
yanlis2["image"] = new Image();
yanlis2.image.src = './images/odaklanma/level/'+RandomSoru+'/yanlis1.png';
yanlis2["x"] = xYanlis2;
yanlis2["y"] = 280;
FocusItem.push(yanlis2);
}
if(focusImageBackground.complete){
if(FocusItem[0].image.complete && FocusItem[1].image.complete && FocusItem[2].image.complete && FocusItem[3].image.complete)
drawFocus();
else
setTimeout(drawFocus,600);
}
else
focusImageBackground.onload=function(){
if(FocusItem[0].image.complete && FocusItem[1].image.complete && FocusItem[2].image.complete && FocusItem[3].image.complete)
drawFocus();
else
setTimeout(drawFocus,600);
}
function drawFocus(){
ftx.drawImage(focusImageBackground,0,0);
for (var i=0; i<FocusItem.length; i++){
FocusItem[i].image.onload=function(){
ftx.drawImage (FocusItem[i].image, FocusItem[i].x, FocusItem[i].y);
}
}
}
I'd suggest loading all your images, then when they are all done, you can call the rest of your code. I don't quite follow what you're trying to do with all the rest of your code, but here's a simple way to load an array of image URLs and know when they are done.
This is the general idea (I've left out lots of your code that has nothing to do with the central issue of knowing when all the images are loaded) and I've also tried to DRY up your code:
function createImagesNotify(srcs, fn) {
var imgs = [], img;
var remaining = srcs.length;
for (var i = 0; i < srcs.length; i++) {
img = new Image();
imgs.push(img);
img.onload = function() {
--remaining;
if (remaining == 0) {
fn(srcs);
}
};
// must set .src after setting onload handler
img.src = srcs[i];
}
return(imgs);
}
// here's your starting array of filenames
var fnames = ["soru.png", "dogru.png", "yanlis.png", "yanlis1.png"];
// insert your process to create RandomSoru here
var randomSoru = ....;
// build full urls array
var urls = [];
for (var i = 0; i < fnames; i++) {
urls.push('./images/odaklanma/level/' + RandomSoru + '/' + fnames[i]);
}
// load all images and call callback function when they are all done loading
var imgs = createImagesNotify(urls, function() {
// all images have been loaded here
// do whatever you want with all the loaded images now (like draw them)
});
This code is based on an earlier answer of mine here: Cross-browser solution for a callback when loading multiple images?

loading an unknown number of images

I'm trying to create a lightbox for my site, and I want it to load all the images from a given directory with a filename like image#.jpg.
This is the code I have:
for(var i=0; i<1000; i++)
{
var filename = "images/image"+i+".jpg";
$.get(filename)
.done(function() {
$('#lightbox').append('<img src="placeholder.gif">');
})
.fail(function() {
i=1000; //ugh
});
}
It kind of works, but only tries to load image1000.jpg.
Also, is there a better way to do something like this? I'm sure saying 'do this a ton of times and stop when I manually change the for loop counter' is frowned on.
If your image names are sequential like your said, you can create a loop for the names, checking at every iteration if image exists - and if it doesn't - break the loop:
var bCheckEnabled = true;
var bFinishCheck = false;
var img;
var imgArray = new Array();
var i = 0;
var myInterval = setInterval(loadImage, 1);
function loadImage() {
if (bFinishCheck) {
clearInterval(myInterval);
alert('Loaded ' + i + ' image(s)!)');
return;
}
if (bCheckEnabled) {
bCheckEnabled = false;
img = new Image();
img.onload = fExists;
img.onerror = fDoesntExist;
img.src = 'images/myFolder/' + i + '.png';
}
}
function fExists() {
imgArray.push(img);
i++;
bCheckEnabled = true;
}

Trying to add image from database but onload not firing

I am trying to draw an image on to a canvas in a Cordova application. I believe I have the Base64 image correct as I have used several methods to encode it and they all seem to return the same data string.
The code below seems to run fine, passing the data into the img.src but the onload does not fire. I have searched probably 50+ questions to see how to do this and they all seem to use the way I am doing it but onload is not firing.
picsSelectAll() is attached to a button in my HTML that is pressed after the page loads, I have also tried to call it in on Deviceready, but it still does the same thing.
Any suggestions on different things to try would be massively appreciated, I have been on this for days now and just can't get past it.
function picsPhotodataSelectHandler(tx, results){
console.log("Photodata had Results: "+results.rows.length);
// Handle the results
for (var i=0; i<results.rows.length; i++) {
var row = results.rows.item(i);
var newImage = new Object();
newImage.picid = row['id'];
newImage.encodedtext = row['img_uri'];
canvasData.push(newImage);
}
showImagesFromDB();
}
function picsSelectAll(){
db.transaction(
function (tx) {
tx.executeSql("SELECT id, img_uri from insc_snags WHERE id=81;", [], picsPhotodataSelectHandler, transaction_error);
}
);
}
function drawImageOnCanvas(data, canvas) {
var img = new Image();
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext("2d").drawImage(img, 0, 0);
};
img.src = data;
}
function getCanvasDataByID(ID){
for (var i=0; i<canvasData.length;i++){
if (canvasData[i].picid == ID) {
return canvasData[i].encodedtext;
}
}
return '';
}
function showImagesFromDB() {
var container = document.getElementById('img-container')
container.innerHTML='';
for (var i=0; i<canvasData.length; i++){
var canvasImg = document.createElement("canvas");
var imgID = canvasData[i].picid;
canvasImg.setAttribute("id", 'cx'+imgID);
//canvasImg.setAttribute("title", data[i].desc);
drawImageOnCanvas(getCanvasDataByID(imgID), canvasImg);
container.appendChild(canvasImg);
}
}

how to check if all images loaded? (no jQuery please)

Due to the nature of async execution in .onload, Javscript would not run in the order as appeared in the code. Following snippet as I leared from how to alert after all images loaded? would not work always, pass here and fail there, all depending on the sequence of browser execution. Any suggestion how to solve it? (no jQuery please, nor asking me why)
var imgLoaded = 0;
var imgToLoad = 10;
var onImgLoad = function()
{
imgLoaded++;
if(imgLoaded == imgToLoad)
{
alert("done"); //Call to our draw function
}
}
for(var i = 0; i < 10; i++)
{
images[i] = new Image();
images[i].onload = onImgLoad;
images[i].src = 'images/'+i+'.png';
}
Specifically, here is where the code fails:
imgLoaded++;
if(imgLoaded == imgToLoad) { }
As a result, sometimes the code in the IF condition might not get executed even if all images were loaded corrected.
See this fiddle : http://jsfiddle.net/wdBbX/
var images = [];
function loadImages(callBack)
{
var imgLoaded = 0;
var imgToLoad = 10;
var onImgLoad = function()
{
imgLoaded++;
if(imgLoaded == imgToLoad)
{
callBack(imgLoaded); //Call to our draw function
}
}
for(var i = 0; i < 10; i++)
{
images[i] = new Image();
images[i].onload = onImgLoad;
images[i].src = 'images/'+i+'.png';
}
}
loadImages(function(i){
alert(i);
});
i prefer the sequentially way in this case.
so you can add cool animations with css...
var current=1,toload=10;
function next(){
var img=document.createElement('img');
img.onload=function(){
current++;
current==(toload+1)?(
console.log('ALL IMAGES ARE LOADED');
// callback
):(
next()
)
document.body.appendChild(this);
}
img.src='img/'+current+'.jpg';
}
window.onload=next;
i also added the way to addan image array
var current=0,images=['img1.jpg','img2.jpg','img3.jpg','img4.jpg'];
function next(){
var img=document.createElement('img');
img.onload=function(){
current++;
current==images.length?(
console.log('ALL IMAGES ARE LOADED');
// callback
):(
next()
)
document.body.appendChild(this);
}
img.src=images[current];
}
window.onload=next;
if your not shure about all images add also a img.onerror with the same function as onload esle the loop stops.
if you don't understand something or you need it as a callback function just ask.
Here is a proper up-to-date solution, including example usage:
const preload = src => new Promise(function(resolve, reject) {
const img = document.createElement('img');
img.onload = function() {
resolve(img);
}
img.onerror = reject;
img.src = src;
});
const preloadAll = sources =>
Promise.all(
sources.map(
preload));
const sources = [
'https://i.picsum.photos/id/1000/5626/3635.jpg',
'https://i.picsum.photos/id/10/2500/1667.jpg',
'https://homepages.cae.wisc.edu/~ece533/images/cat.png',
'https://homepages.cae.wisc.edu/~ece533/images/airplane.png'
];
preloadAll(sources)
.then(images => console.log('Preloaded all', images))
.catch(err => console.error('Failed', err));
From here.

Categories